From d783cec97084a12873ca62b5fcd64620056ec01b Mon Sep 17 00:00:00 2001 From: Christian Schneppe Date: Sat, 19 Nov 2016 23:07:54 +0100 Subject: reformat code --- src/main/java/de/pixart/messenger/Config.java | 2 +- .../java/de/pixart/messenger/OmemoActivity.java | 16 +- .../de/pixart/messenger/crypto/OtrService.java | 486 +- .../messenger/crypto/PgpDecryptionService.java | 24 +- .../java/de/pixart/messenger/crypto/PgpEngine.java | 536 +- .../messenger/crypto/XmppDomainVerifier.java | 188 +- .../messenger/crypto/axolotl/AxolotlService.java | 2090 +++--- .../crypto/axolotl/CryptoFailedException.java | 6 +- .../crypto/axolotl/FingerprintStatus.java | 6 +- .../crypto/axolotl/NoSessionsCreatedException.java | 2 +- .../crypto/axolotl/OnMessageCreatedCallback.java | 2 +- .../crypto/axolotl/SQLiteAxolotlStore.java | 834 +-- .../crypto/axolotl/XmppAxolotlMessage.java | 446 +- .../crypto/axolotl/XmppAxolotlSession.java | 218 +- .../de/pixart/messenger/crypto/sasl/Anonymous.java | 30 +- .../de/pixart/messenger/crypto/sasl/DigestMd5.java | 140 +- .../de/pixart/messenger/crypto/sasl/External.java | 36 +- .../de/pixart/messenger/crypto/sasl/Plain.java | 32 +- .../messenger/crypto/sasl/SaslMechanism.java | 107 +- .../de/pixart/messenger/crypto/sasl/ScramSha1.java | 423 +- .../de/pixart/messenger/crypto/sasl/Tokenizer.java | 120 +- .../pixart/messenger/entities/AbstractEntity.java | 18 +- .../java/de/pixart/messenger/entities/Account.java | 1258 ++-- .../de/pixart/messenger/entities/Blockable.java | 14 +- .../de/pixart/messenger/entities/Bookmark.java | 312 +- .../java/de/pixart/messenger/entities/Contact.java | 1059 +-- .../de/pixart/messenger/entities/Conversation.java | 1888 ++--- .../messenger/entities/DownloadableFile.java | 148 +- .../de/pixart/messenger/entities/ListItem.java | 38 +- .../java/de/pixart/messenger/entities/Message.java | 5 +- .../de/pixart/messenger/entities/MucOptions.java | 1320 ++-- .../de/pixart/messenger/entities/Presence.java | 170 +- .../messenger/entities/PresenceTemplate.java | 134 +- .../de/pixart/messenger/entities/Presences.java | 212 +- .../java/de/pixart/messenger/entities/Roster.java | 148 +- .../messenger/entities/ServiceDiscoveryResult.java | 648 +- .../de/pixart/messenger/entities/Transferable.java | 2 +- .../entities/TransferablePlaceholder.java | 59 +- .../messenger/generator/AbstractGenerator.java | 182 +- .../de/pixart/messenger/generator/IqGenerator.java | 687 +- .../messenger/generator/MessageGenerator.java | 400 +- .../messenger/generator/PresenceGenerator.java | 94 +- .../messenger/http/HttpConnectionManager.java | 142 +- .../messenger/http/HttpDownloadConnection.java | 662 +- .../messenger/http/HttpUploadConnection.java | 388 +- .../de/pixart/messenger/parser/AbstractParser.java | 130 +- .../java/de/pixart/messenger/parser/IqParser.java | 652 +- .../de/pixart/messenger/parser/MessageParser.java | 1218 ++-- .../de/pixart/messenger/parser/PresenceParser.java | 454 +- .../messenger/persistance/DatabaseBackend.java | 2510 +++---- .../pixart/messenger/persistance/FileBackend.java | 1828 ++--- .../persistance/OnPhoneContactsMerged.java | 2 +- .../services/AbstractConnectionManager.java | 188 +- .../pixart/messenger/services/AlarmReceiver.java | 2 +- .../pixart/messenger/services/AvatarService.java | 802 +-- .../messenger/services/CheckAppVersionService.java | 2 +- .../services/ContactChooserTargetService.java | 104 +- .../pixart/messenger/services/EventReceiver.java | 28 +- .../messenger/services/ExportLogsService.java | 328 +- .../messenger/services/MessageArchiveService.java | 764 +- .../messenger/services/NotificationService.java | 8 +- .../messenger/services/XmppConnectionService.java | 7565 ++++++++++---------- .../de/pixart/messenger/ui/AboutPreference.java | 20 +- .../ui/AbstractSearchableListItemActivity.java | 202 +- .../de/pixart/messenger/ui/BlockContactDialog.java | 84 +- .../de/pixart/messenger/ui/BlocklistActivity.java | 102 +- .../messenger/ui/ChangePasswordActivity.java | 194 +- .../pixart/messenger/ui/ChooseContactActivity.java | 428 +- .../messenger/ui/ConferenceDetailsActivity.java | 1238 ++-- .../messenger/ui/ContactDetailsActivity.java | 996 +-- .../pixart/messenger/ui/ConversationActivity.java | 3452 ++++----- .../pixart/messenger/ui/ConversationFragment.java | 2630 +++---- .../pixart/messenger/ui/EditAccountActivity.java | 2200 +++--- .../java/de/pixart/messenger/ui/EditMessage.java | 161 +- .../de/pixart/messenger/ui/EnterJidDialog.java | 214 +- .../pixart/messenger/ui/MagicCreateActivity.java | 192 +- .../pixart/messenger/ui/ManageAccountActivity.java | 688 +- .../ui/PublishProfilePictureActivity.java | 547 +- .../de/pixart/messenger/ui/RecordingActivity.java | 254 +- .../pixart/messenger/ui/SetPresenceActivity.java | 400 +- .../de/pixart/messenger/ui/SettingsActivity.java | 468 +- .../de/pixart/messenger/ui/SettingsFragment.java | 80 +- .../pixart/messenger/ui/ShareLocationActivity.java | 90 +- .../de/pixart/messenger/ui/ShareWithActivity.java | 602 +- .../ui/ShowFullscreenMessageActivity.java | 4 +- .../pixart/messenger/ui/ShowLocationActivity.java | 100 +- .../messenger/ui/StartConversationActivity.java | 4 +- src/main/java/de/pixart/messenger/ui/StartUI.java | 4 +- .../de/pixart/messenger/ui/TimePreference.java | 178 +- .../de/pixart/messenger/ui/TrustKeysActivity.java | 606 +- .../java/de/pixart/messenger/ui/UiCallback.java | 6 +- .../de/pixart/messenger/ui/VerifyOTRActivity.java | 770 +- .../de/pixart/messenger/ui/WelcomeActivity.java | 24 +- .../java/de/pixart/messenger/ui/XmppActivity.java | 2410 +++---- .../messenger/ui/adapter/AccountAdapter.java | 74 +- .../messenger/ui/adapter/ConversationAdapter.java | 116 +- .../messenger/ui/adapter/KnownHostsAdapter.java | 108 +- .../messenger/ui/adapter/ListItemAdapter.java | 302 +- .../messenger/ui/adapter/MessageAdapter.java | 1874 ++--- .../ui/forms/FormBooleanFieldWrapper.java | 114 +- .../messenger/ui/forms/FormFieldFactory.java | 35 +- .../messenger/ui/forms/FormFieldWrapper.java | 152 +- .../ui/forms/FormJidSingleFieldWrapper.java | 54 +- .../messenger/ui/forms/FormTextFieldWrapper.java | 138 +- .../de/pixart/messenger/ui/forms/FormWrapper.java | 98 +- .../ui/widget/ClickableMovementMethod.java | 56 +- .../java/de/pixart/messenger/ui/widget/Switch.java | 102 +- .../messenger/utils/ConversationsFileObserver.java | 14 +- .../de/pixart/messenger/utils/CryptoHelper.java | 370 +- .../java/de/pixart/messenger/utils/DNSHelper.java | 464 +- .../pixart/messenger/utils/ExceptionHandler.java | 32 +- .../de/pixart/messenger/utils/ExceptionHelper.java | 195 +- .../java/de/pixart/messenger/utils/ExifHelper.java | 4 +- .../java/de/pixart/messenger/utils/FileUtils.java | 282 +- .../java/de/pixart/messenger/utils/GeoHelper.java | 120 +- .../java/de/pixart/messenger/utils/MimeUtils.java | 15 + .../utils/OnPhoneContactsLoadedListener.java | 2 +- .../java/de/pixart/messenger/utils/PRNGFixes.java | 519 +- .../de/pixart/messenger/utils/PhoneHelper.java | 238 +- .../de/pixart/messenger/utils/SSLSocketHelper.java | 102 +- .../utils/SerialSingleThreadExecutor.java | 78 +- .../pixart/messenger/utils/SocksSocketFactory.java | 84 +- .../java/de/pixart/messenger/utils/UIHelper.java | 556 +- .../java/de/pixart/messenger/utils/XmlHelper.java | 18 +- src/main/java/de/pixart/messenger/utils/Xmlns.java | 10 +- .../java/de/pixart/messenger/utils/XmppUri.java | 271 +- .../messenger/utils/video/MediaController.java | 2 +- .../messenger/utils/video/TextureRenderer.java | 28 +- .../de/pixart/messenger/utils/video/Track.java | 6 +- src/main/java/de/pixart/messenger/xml/Element.java | 348 +- src/main/java/de/pixart/messenger/xml/Tag.java | 186 +- .../java/de/pixart/messenger/xml/TagWriter.java | 162 +- .../java/de/pixart/messenger/xml/XmlReader.java | 202 +- .../xmpp/OnAdvancedStreamFeaturesLoaded.java | 2 +- .../de/pixart/messenger/xmpp/OnBindListener.java | 2 +- .../messenger/xmpp/OnContactStatusChanged.java | 2 +- .../pixart/messenger/xmpp/OnIqPacketReceived.java | 2 +- .../pixart/messenger/xmpp/OnKeyStatusUpdated.java | 2 +- .../messenger/xmpp/OnMessageAcknowledged.java | 2 +- .../messenger/xmpp/OnMessagePacketReceived.java | 2 +- .../messenger/xmpp/OnPresencePacketReceived.java | 2 +- .../de/pixart/messenger/xmpp/OnStatusChanged.java | 2 +- .../pixart/messenger/xmpp/OnUpdateBlocklist.java | 16 +- .../de/pixart/messenger/xmpp/XmppConnection.java | 3142 ++++---- .../pixart/messenger/xmpp/chatstate/ChatState.java | 46 +- .../java/de/pixart/messenger/xmpp/forms/Data.java | 174 +- .../java/de/pixart/messenger/xmpp/forms/Field.java | 116 +- .../messenger/xmpp/jid/InvalidJidException.java | 4 +- .../java/de/pixart/messenger/xmpp/jid/Jid.java | 398 +- .../messenger/xmpp/jingle/JingleCandidate.java | 250 +- .../messenger/xmpp/jingle/JingleConnection.java | 2038 +++--- .../xmpp/jingle/JingleConnectionManager.java | 290 +- .../xmpp/jingle/JingleInbandTransport.java | 424 +- .../xmpp/jingle/JingleSocks5Transport.java | 374 +- .../messenger/xmpp/jingle/JingleTransport.java | 12 +- .../jingle/OnFileTransmissionStatusChanged.java | 4 +- .../xmpp/jingle/OnJinglePacketReceived.java | 2 +- .../xmpp/jingle/OnPrimaryCandidateFound.java | 2 +- .../xmpp/jingle/OnTransportConnected.java | 4 +- .../messenger/xmpp/jingle/stanzas/Content.java | 242 +- .../xmpp/jingle/stanzas/JinglePacket.java | 150 +- .../messenger/xmpp/jingle/stanzas/Reason.java | 12 +- .../java/de/pixart/messenger/xmpp/pep/Avatar.java | 172 +- .../stanzas/AbstractAcknowledgeableStanza.java | 40 +- .../messenger/xmpp/stanzas/AbstractStanza.java | 82 +- .../de/pixart/messenger/xmpp/stanzas/IqPacket.java | 110 +- .../messenger/xmpp/stanzas/MessagePacket.java | 164 +- .../messenger/xmpp/stanzas/PresencePacket.java | 6 +- .../messenger/xmpp/stanzas/csi/ActivePacket.java | 8 +- .../messenger/xmpp/stanzas/csi/InactivePacket.java | 8 +- .../xmpp/stanzas/streammgmt/AckPacket.java | 10 +- .../xmpp/stanzas/streammgmt/EnablePacket.java | 10 +- .../xmpp/stanzas/streammgmt/RequestPacket.java | 8 +- .../xmpp/stanzas/streammgmt/ResumePacket.java | 12 +- 174 files changed, 33791 insertions(+), 33753 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/de/pixart/messenger/Config.java b/src/main/java/de/pixart/messenger/Config.java index 61422938c..f44ab661b 100644 --- a/src/main/java/de/pixart/messenger/Config.java +++ b/src/main/java/de/pixart/messenger/Config.java @@ -116,7 +116,7 @@ public final class Config { public static final boolean PARSE_REAL_JID_FROM_MUC_MAM = false; //dangerous if server doesn’t filter public static final long MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000; - public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY / 2; + public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY / 2; public static final int MAM_MAX_MESSAGES = 500; public static final long FREQUENT_RESTARTS_DETECTION_WINDOW = 8 * 60 * 60 * 1000; // 8 hours diff --git a/src/main/java/de/pixart/messenger/OmemoActivity.java b/src/main/java/de/pixart/messenger/OmemoActivity.java index f60b064cf..dcf420442 100644 --- a/src/main/java/de/pixart/messenger/OmemoActivity.java +++ b/src/main/java/de/pixart/messenger/OmemoActivity.java @@ -28,7 +28,7 @@ public abstract class OmemoActivity extends XmppActivity { @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu,v,menuInfo); + super.onCreateContextMenu(menu, v, menuInfo); Object account = v.getTag(R.id.TAG_ACCOUNT); Object fingerprint = v.getTag(R.id.TAG_FINGERPRINT); if (account != null && fingerprint != null && account instanceof Account && fingerprint instanceof String) { @@ -42,7 +42,7 @@ public abstract class OmemoActivity extends XmppActivity { public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.purge_omemo_key: - showPurgeKeyDialog(mSelectedAccount,mSelectedFingerprint); + showPurgeKeyDialog(mSelectedAccount, mSelectedFingerprint); break; case R.id.copy_omemo_key: copyOmemoFingerprint(mSelectedFingerprint); @@ -88,7 +88,7 @@ public abstract class OmemoActivity extends XmppActivity { View.OnClickListener listener = new View.OnClickListener() { @Override public void onClick(View v) { - showX509Certificate(account,fingerprint); + showX509Certificate(account, fingerprint); } }; key.setOnClickListener(listener); @@ -98,13 +98,13 @@ public abstract class OmemoActivity extends XmppActivity { ImageView verifiedFingerprintSymbol = (ImageView) view.findViewById(R.id.verified_fingerprint); trustToggle.setVisibility(View.VISIBLE); registerForContextMenu(view); - view.setTag(R.id.TAG_ACCOUNT,account); - view.setTag(R.id.TAG_FINGERPRINT,fingerprint); + view.setTag(R.id.TAG_ACCOUNT, account); + view.setTag(R.id.TAG_FINGERPRINT, fingerprint); boolean x509 = Config.X509_VERIFICATION && status.getTrust() == FingerprintStatus.Trust.VERIFIED_X509; final View.OnClickListener toast; trustToggle.setChecked(status.isTrusted(), false); - if (status.isActive()){ + if (status.isActive()) { key.setTextColor(getPrimaryTextColor()); keyType.setTextColor(getSecondaryTextColor()); if (status.isVerified()) { @@ -126,7 +126,7 @@ public abstract class OmemoActivity extends XmppActivity { trustToggle.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - account.getAxolotlService().setFingerprintTrust(fingerprint,FingerprintStatus.createActive(false)); + account.getAxolotlService().setFingerprintTrust(fingerprint, FingerprintStatus.createActive(false)); v.setEnabled(true); v.setOnClickListener(null); } @@ -211,7 +211,7 @@ public abstract class OmemoActivity extends XmppActivity { if (x509Certificate != null) { showCertificateInformationDialog(CryptoHelper.extractCertificateInformation(x509Certificate)); } else { - Toast.makeText(this,R.string.certificate_not_found, Toast.LENGTH_SHORT).show(); + Toast.makeText(this, R.string.certificate_not_found, Toast.LENGTH_SHORT).show(); } } diff --git a/src/main/java/de/pixart/messenger/crypto/OtrService.java b/src/main/java/de/pixart/messenger/crypto/OtrService.java index 18f9670d8..459f49c38 100644 --- a/src/main/java/de/pixart/messenger/crypto/OtrService.java +++ b/src/main/java/de/pixart/messenger/crypto/OtrService.java @@ -38,25 +38,25 @@ import de.pixart.messenger.xmpp.stanzas.MessagePacket; public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost { - private Account account; - private OtrPolicy otrPolicy; - private KeyPair keyPair; - private XmppConnectionService mXmppConnectionService; - - public OtrService(XmppConnectionService service, Account account) { - this.account = account; - this.otrPolicy = new OtrPolicyImpl(); - this.otrPolicy.setAllowV1(false); - this.otrPolicy.setAllowV2(true); - this.otrPolicy.setAllowV3(true); - this.keyPair = loadKey(account.getKeys()); - this.mXmppConnectionService = service; - } - - private KeyPair loadKey(final JSONObject keys) { - if (keys == null) { - return null; - } + private Account account; + private OtrPolicy otrPolicy; + private KeyPair keyPair; + private XmppConnectionService mXmppConnectionService; + + public OtrService(XmppConnectionService service, Account account) { + this.account = account; + this.otrPolicy = new OtrPolicyImpl(); + this.otrPolicy.setAllowV1(false); + this.otrPolicy.setAllowV2(true); + this.otrPolicy.setAllowV3(true); + this.keyPair = loadKey(account.getKeys()); + this.mXmppConnectionService = service; + } + + private KeyPair loadKey(final JSONObject keys) { + if (keys == null) { + return null; + } synchronized (keys) { try { BigInteger x = new BigInteger(keys.getString("otr_x"), 16); @@ -78,232 +78,232 @@ public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost { return null; } } - } - - private void saveKey() { - PublicKey publicKey = keyPair.getPublic(); - PrivateKey privateKey = keyPair.getPrivate(); - KeyFactory keyFactory; - try { - keyFactory = KeyFactory.getInstance("DSA"); - DSAPrivateKeySpec privateKeySpec = keyFactory.getKeySpec( - privateKey, DSAPrivateKeySpec.class); - DSAPublicKeySpec publicKeySpec = keyFactory.getKeySpec(publicKey, - DSAPublicKeySpec.class); - this.account.setKey("otr_x", privateKeySpec.getX().toString(16)); - this.account.setKey("otr_g", privateKeySpec.getG().toString(16)); - this.account.setKey("otr_p", privateKeySpec.getP().toString(16)); - this.account.setKey("otr_q", privateKeySpec.getQ().toString(16)); - this.account.setKey("otr_y", publicKeySpec.getY().toString(16)); - } catch (final NoSuchAlgorithmException | InvalidKeySpecException e) { - e.printStackTrace(); - } + } + + private void saveKey() { + PublicKey publicKey = keyPair.getPublic(); + PrivateKey privateKey = keyPair.getPrivate(); + KeyFactory keyFactory; + try { + keyFactory = KeyFactory.getInstance("DSA"); + DSAPrivateKeySpec privateKeySpec = keyFactory.getKeySpec( + privateKey, DSAPrivateKeySpec.class); + DSAPublicKeySpec publicKeySpec = keyFactory.getKeySpec(publicKey, + DSAPublicKeySpec.class); + this.account.setKey("otr_x", privateKeySpec.getX().toString(16)); + this.account.setKey("otr_g", privateKeySpec.getG().toString(16)); + this.account.setKey("otr_p", privateKeySpec.getP().toString(16)); + this.account.setKey("otr_q", privateKeySpec.getQ().toString(16)); + this.account.setKey("otr_y", publicKeySpec.getY().toString(16)); + } catch (final NoSuchAlgorithmException | InvalidKeySpecException e) { + e.printStackTrace(); + } + + } + + @Override + public void askForSecret(SessionID id, InstanceTag instanceTag, String question) { + try { + final Jid jid = Jid.fromSessionID(id); + Conversation conversation = this.mXmppConnectionService.find(this.account, jid); + if (conversation != null) { + conversation.smp().hint = question; + conversation.smp().status = Conversation.Smp.STATUS_CONTACT_REQUESTED; + mXmppConnectionService.updateConversationUi(); + } + } catch (InvalidJidException e) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": smp in invalid session " + id.toString()); + } + } + + @Override + public void finishedSessionMessage(SessionID arg0, String arg1) + throws OtrException { + + } + + @Override + public String getFallbackMessage(SessionID arg0) { + return MessageGenerator.OTR_FALLBACK_MESSAGE; + } + + @Override + public byte[] getLocalFingerprintRaw(SessionID arg0) { + try { + return getFingerprintRaw(getPublicKey()); + } catch (OtrCryptoException e) { + return null; + } + } + + public PublicKey getPublicKey() { + if (this.keyPair == null) { + return null; + } + return this.keyPair.getPublic(); + } + + @Override + public KeyPair getLocalKeyPair(SessionID arg0) throws OtrException { + if (this.keyPair == null) { + KeyPairGenerator kg; + try { + kg = KeyPairGenerator.getInstance("DSA"); + this.keyPair = kg.genKeyPair(); + this.saveKey(); + mXmppConnectionService.databaseBackend.updateAccount(account); + } catch (NoSuchAlgorithmException e) { + Log.d(Config.LOGTAG, + "error generating key pair " + e.getMessage()); + } + } + return this.keyPair; + } + + @Override + public String getReplyForUnreadableMessage(SessionID arg0) { + // TODO Auto-generated method stub + return null; + } + + @Override + public OtrPolicy getSessionPolicy(SessionID arg0) { + return otrPolicy; + } + + @Override + public void injectMessage(SessionID session, String body) + throws OtrException { + MessagePacket packet = new MessagePacket(); + packet.setFrom(account.getJid()); + if (session.getUserID().isEmpty()) { + packet.setAttribute("to", session.getAccountID()); + } else { + packet.setAttribute("to", session.getAccountID() + "/" + session.getUserID()); + } + packet.setBody(body); + MessageGenerator.addMessageHints(packet); + try { + Jid jid = Jid.fromSessionID(session); + Conversation conversation = mXmppConnectionService.find(account, jid); + if (conversation != null && conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) { + if (mXmppConnectionService.sendChatStates()) { + packet.addChild(ChatState.toElement(conversation.getOutgoingChatState())); + } + } + } catch (final InvalidJidException ignored) { + + } + + packet.setType(MessagePacket.TYPE_CHAT); + account.getXmppConnection().sendMessagePacket(packet); + } + + @Override + public void messageFromAnotherInstanceReceived(SessionID session) { + sendOtrErrorMessage(session, "Message from another OTR-instance received"); + } + + @Override + public void multipleInstancesDetected(SessionID arg0) { + // TODO Auto-generated method stub + + } + + @Override + public void requireEncryptedMessage(SessionID arg0, String arg1) + throws OtrException { + // TODO Auto-generated method stub + + } + + @Override + public void showError(SessionID arg0, String arg1) throws OtrException { + Log.d(Config.LOGTAG, "show error"); + } + + @Override + public void smpAborted(SessionID id) throws OtrException { + setSmpStatus(id, Conversation.Smp.STATUS_NONE); + } + + private void setSmpStatus(SessionID id, int status) { + try { + final Jid jid = Jid.fromSessionID(id); + Conversation conversation = this.mXmppConnectionService.find(this.account, jid); + if (conversation != null) { + conversation.smp().status = status; + mXmppConnectionService.updateConversationUi(); + } + } catch (final InvalidJidException ignored) { + + } + } + + @Override + public void smpError(SessionID id, int arg1, boolean arg2) + throws OtrException { + setSmpStatus(id, Conversation.Smp.STATUS_NONE); + } + + @Override + public void unencryptedMessageReceived(SessionID arg0, String arg1) + throws OtrException { + throw new OtrException(new Exception("unencrypted message received")); + } + + @Override + public void unreadableMessageReceived(SessionID session) throws OtrException { + Log.d(Config.LOGTAG, "unreadable message received"); + sendOtrErrorMessage(session, "You sent me an unreadable OTR-encrypted message"); + } + + public void sendOtrErrorMessage(SessionID session, String errorText) { + try { + Jid jid = Jid.fromSessionID(session); + Conversation conversation = mXmppConnectionService.find(account, jid); + String id = conversation == null ? null : conversation.getLastReceivedOtrMessageId(); + if (id != null) { + MessagePacket packet = mXmppConnectionService.getMessageGenerator() + .generateOtrError(jid, id, errorText); + packet.setFrom(account.getJid()); + mXmppConnectionService.sendMessagePacket(account, packet); + Log.d(Config.LOGTAG, packet.toString()); + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + + ": unreadable OTR message in " + conversation.getName()); + } + } catch (InvalidJidException e) { + return; + } + } + @Override + public void unverify(SessionID id, String arg1) { + setSmpStatus(id, Conversation.Smp.STATUS_FAILED); } - @Override - public void askForSecret(SessionID id, InstanceTag instanceTag, String question) { - try { - final Jid jid = Jid.fromSessionID(id); - Conversation conversation = this.mXmppConnectionService.find(this.account,jid); - if (conversation!=null) { - conversation.smp().hint = question; - conversation.smp().status = Conversation.Smp.STATUS_CONTACT_REQUESTED; - mXmppConnectionService.updateConversationUi(); - } - } catch (InvalidJidException e) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": smp in invalid session "+id.toString()); - } - } - - @Override - public void finishedSessionMessage(SessionID arg0, String arg1) - throws OtrException { - - } - - @Override - public String getFallbackMessage(SessionID arg0) { - return MessageGenerator.OTR_FALLBACK_MESSAGE; - } - - @Override - public byte[] getLocalFingerprintRaw(SessionID arg0) { - try { - return getFingerprintRaw(getPublicKey()); - } catch (OtrCryptoException e) { - return null; - } - } - - public PublicKey getPublicKey() { - if (this.keyPair == null) { - return null; - } - return this.keyPair.getPublic(); - } - - @Override - public KeyPair getLocalKeyPair(SessionID arg0) throws OtrException { - if (this.keyPair == null) { - KeyPairGenerator kg; - try { - kg = KeyPairGenerator.getInstance("DSA"); - this.keyPair = kg.genKeyPair(); - this.saveKey(); - mXmppConnectionService.databaseBackend.updateAccount(account); - } catch (NoSuchAlgorithmException e) { - Log.d(Config.LOGTAG, - "error generating key pair " + e.getMessage()); - } - } - return this.keyPair; - } - - @Override - public String getReplyForUnreadableMessage(SessionID arg0) { - // TODO Auto-generated method stub - return null; - } - - @Override - public OtrPolicy getSessionPolicy(SessionID arg0) { - return otrPolicy; - } - - @Override - public void injectMessage(SessionID session, String body) - throws OtrException { - MessagePacket packet = new MessagePacket(); - packet.setFrom(account.getJid()); - if (session.getUserID().isEmpty()) { - packet.setAttribute("to", session.getAccountID()); - } else { - packet.setAttribute("to", session.getAccountID() + "/" + session.getUserID()); - } - packet.setBody(body); - MessageGenerator.addMessageHints(packet); - try { - Jid jid = Jid.fromSessionID(session); - Conversation conversation = mXmppConnectionService.find(account,jid); - if (conversation != null && conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) { - if (mXmppConnectionService.sendChatStates()) { - packet.addChild(ChatState.toElement(conversation.getOutgoingChatState())); - } - } - } catch (final InvalidJidException ignored) { - - } - - packet.setType(MessagePacket.TYPE_CHAT); - account.getXmppConnection().sendMessagePacket(packet); - } - - @Override - public void messageFromAnotherInstanceReceived(SessionID session) { - sendOtrErrorMessage(session, "Message from another OTR-instance received"); - } - - @Override - public void multipleInstancesDetected(SessionID arg0) { - // TODO Auto-generated method stub - - } - - @Override - public void requireEncryptedMessage(SessionID arg0, String arg1) - throws OtrException { - // TODO Auto-generated method stub - - } - - @Override - public void showError(SessionID arg0, String arg1) throws OtrException { - Log.d(Config.LOGTAG,"show error"); - } - - @Override - public void smpAborted(SessionID id) throws OtrException { - setSmpStatus(id, Conversation.Smp.STATUS_NONE); - } - - private void setSmpStatus(SessionID id, int status) { - try { - final Jid jid = Jid.fromSessionID(id); - Conversation conversation = this.mXmppConnectionService.find(this.account,jid); - if (conversation!=null) { - conversation.smp().status = status; - mXmppConnectionService.updateConversationUi(); - } - } catch (final InvalidJidException ignored) { - - } - } - - @Override - public void smpError(SessionID id, int arg1, boolean arg2) - throws OtrException { - setSmpStatus(id, Conversation.Smp.STATUS_NONE); - } - - @Override - public void unencryptedMessageReceived(SessionID arg0, String arg1) - throws OtrException { - throw new OtrException(new Exception("unencrypted message received")); - } - - @Override - public void unreadableMessageReceived(SessionID session) throws OtrException { - Log.d(Config.LOGTAG,"unreadable message received"); - sendOtrErrorMessage(session, "You sent me an unreadable OTR-encrypted message"); - } - - public void sendOtrErrorMessage(SessionID session, String errorText) { - try { - Jid jid = Jid.fromSessionID(session); - Conversation conversation = mXmppConnectionService.find(account, jid); - String id = conversation == null ? null : conversation.getLastReceivedOtrMessageId(); - if (id != null) { - MessagePacket packet = mXmppConnectionService.getMessageGenerator() - .generateOtrError(jid, id, errorText); - packet.setFrom(account.getJid()); - mXmppConnectionService.sendMessagePacket(account,packet); - Log.d(Config.LOGTAG,packet.toString()); - Log.d(Config.LOGTAG,account.getJid().toBareJid().toString() - +": unreadable OTR message in "+conversation.getName()); - } - } catch (InvalidJidException e) { - return; - } - } - - @Override - public void unverify(SessionID id, String arg1) { - setSmpStatus(id, Conversation.Smp.STATUS_FAILED); - } - - @Override - public void verify(SessionID id, String fingerprint, boolean approved) { - Log.d(Config.LOGTAG,"OtrService.verify("+id.toString()+","+fingerprint+","+String.valueOf(approved)+")"); - try { - final Jid jid = Jid.fromSessionID(id); - Conversation conversation = this.mXmppConnectionService.find(this.account,jid); - if (conversation!=null) { - if (approved) { - conversation.getContact().addOtrFingerprint(fingerprint); - } - conversation.smp().hint = null; - conversation.smp().status = Conversation.Smp.STATUS_VERIFIED; - mXmppConnectionService.updateConversationUi(); - mXmppConnectionService.syncRosterToDisk(conversation.getAccount()); - } - } catch (final InvalidJidException ignored) { - } - } - - @Override - public FragmenterInstructions getFragmenterInstructions(SessionID sessionID) { - return null; - } + @Override + public void verify(SessionID id, String fingerprint, boolean approved) { + Log.d(Config.LOGTAG, "OtrService.verify(" + id.toString() + "," + fingerprint + "," + String.valueOf(approved) + ")"); + try { + final Jid jid = Jid.fromSessionID(id); + Conversation conversation = this.mXmppConnectionService.find(this.account, jid); + if (conversation != null) { + if (approved) { + conversation.getContact().addOtrFingerprint(fingerprint); + } + conversation.smp().hint = null; + conversation.smp().status = Conversation.Smp.STATUS_VERIFIED; + mXmppConnectionService.updateConversationUi(); + mXmppConnectionService.syncRosterToDisk(conversation.getAccount()); + } + } catch (final InvalidJidException ignored) { + } + } + + @Override + public FragmenterInstructions getFragmenterInstructions(SessionID sessionID) { + return null; + } } diff --git a/src/main/java/de/pixart/messenger/crypto/PgpDecryptionService.java b/src/main/java/de/pixart/messenger/crypto/PgpDecryptionService.java index ffa853fda..4b0e78764 100644 --- a/src/main/java/de/pixart/messenger/crypto/PgpDecryptionService.java +++ b/src/main/java/de/pixart/messenger/crypto/PgpDecryptionService.java @@ -28,9 +28,9 @@ public class PgpDecryptionService { private final XmppConnectionService mXmppConnectionService; private OpenPgpApi openPgpApi = null; - protected final ArrayDeque messages = new ArrayDeque(); + protected final ArrayDeque messages = new ArrayDeque(); protected final HashSet pendingNotifications = new HashSet<>(); - Message currentMessage; + Message currentMessage; private PendingIntent pendingIntent; @@ -38,7 +38,7 @@ public class PgpDecryptionService { this.mXmppConnectionService = service; } - public synchronized boolean decrypt(final Message message, boolean notify) { + public synchronized boolean decrypt(final Message message, boolean notify) { messages.add(message); if (notify && pendingIntent == null) { pendingNotifications.add(message); @@ -48,10 +48,10 @@ public class PgpDecryptionService { continueDecryption(); return notify; } - } + } public synchronized void decrypt(final List list) { - for(Message message : list) { + for (Message message : list) { if (message.getEncryption() == Message.ENCRYPTION_PGP) { messages.add(message); } @@ -69,19 +69,19 @@ public class PgpDecryptionService { this.pendingNotifications.remove(message); } - protected synchronized void decryptNext() { - if (pendingIntent == null + protected synchronized void decryptNext() { + if (pendingIntent == null && getOpenPgpApi() != null - && (currentMessage = messages.poll()) != null) { - new Thread(new Runnable() { + && (currentMessage = messages.poll()) != null) { + new Thread(new Runnable() { @Override public void run() { executeApi(currentMessage); decryptNext(); } }).start(); - } - } + } + } public synchronized void continueDecryption(boolean resetPending) { if (resetPending) { @@ -200,7 +200,7 @@ public class PgpDecryptionService { if (pendingIntent == null) { return false; } else { - for(Message message : messages) { + for (Message message : messages) { if (message.getConversation() == conversation) { return true; } diff --git a/src/main/java/de/pixart/messenger/crypto/PgpEngine.java b/src/main/java/de/pixart/messenger/crypto/PgpEngine.java index 38d097ec3..c906c7514 100644 --- a/src/main/java/de/pixart/messenger/crypto/PgpEngine.java +++ b/src/main/java/de/pixart/messenger/crypto/PgpEngine.java @@ -29,290 +29,290 @@ import de.pixart.messenger.services.XmppConnectionService; import de.pixart.messenger.ui.UiCallback; public class PgpEngine { - private OpenPgpApi api; - private XmppConnectionService mXmppConnectionService; + private OpenPgpApi api; + private XmppConnectionService mXmppConnectionService; - public PgpEngine(OpenPgpApi api, XmppConnectionService service) { - this.api = api; - this.mXmppConnectionService = service; - } + public PgpEngine(OpenPgpApi api, XmppConnectionService service) { + this.api = api; + this.mXmppConnectionService = service; + } - public void encrypt(final Message message, final UiCallback callback) { - Intent params = new Intent(); - params.setAction(OpenPgpApi.ACTION_ENCRYPT); - final Conversation conversation = message.getConversation(); - if (conversation.getMode() == Conversation.MODE_SINGLE) { - long[] keys = { - conversation.getContact().getPgpKeyId(), - conversation.getAccount().getPgpId() - }; - params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keys); - } else { - params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, conversation.getMucOptions().getPgpKeyIds()); - } + public void encrypt(final Message message, final UiCallback callback) { + Intent params = new Intent(); + params.setAction(OpenPgpApi.ACTION_ENCRYPT); + final Conversation conversation = message.getConversation(); + if (conversation.getMode() == Conversation.MODE_SINGLE) { + long[] keys = { + conversation.getContact().getPgpKeyId(), + conversation.getAccount().getPgpId() + }; + params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keys); + } else { + params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, conversation.getMucOptions().getPgpKeyIds()); + } - if (!message.needsUploading()) { - params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); - String body; - if (message.hasFileOnRemoteHost()) { - body = message.getFileParams().url.toString(); - } else { - body = message.getBody(); - } - InputStream is = new ByteArrayInputStream(body.getBytes()); - final OutputStream os = new ByteArrayOutputStream(); - api.executeApiAsync(params, is, os, new IOpenPgpCallback() { + if (!message.needsUploading()) { + params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); + String body; + if (message.hasFileOnRemoteHost()) { + body = message.getFileParams().url.toString(); + } else { + body = message.getBody(); + } + InputStream is = new ByteArrayInputStream(body.getBytes()); + final OutputStream os = new ByteArrayOutputStream(); + api.executeApiAsync(params, is, os, new IOpenPgpCallback() { - @Override - public void onReturn(Intent result) { - switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, - OpenPgpApi.RESULT_CODE_ERROR)) { - case OpenPgpApi.RESULT_CODE_SUCCESS: - try { - os.flush(); - StringBuilder encryptedMessageBody = new StringBuilder(); - String[] lines = os.toString().split("\n"); - for (int i = 2; i < lines.length - 1; ++i) { - if (!lines[i].contains("Version")) { - encryptedMessageBody.append(lines[i].trim()); - } - } - message.setEncryptedBody(encryptedMessageBody - .toString()); - callback.success(message); - } catch (IOException e) { - callback.error(R.string.openpgp_error, message); - } + @Override + public void onReturn(Intent result) { + switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, + OpenPgpApi.RESULT_CODE_ERROR)) { + case OpenPgpApi.RESULT_CODE_SUCCESS: + try { + os.flush(); + StringBuilder encryptedMessageBody = new StringBuilder(); + String[] lines = os.toString().split("\n"); + for (int i = 2; i < lines.length - 1; ++i) { + if (!lines[i].contains("Version")) { + encryptedMessageBody.append(lines[i].trim()); + } + } + message.setEncryptedBody(encryptedMessageBody + .toString()); + callback.success(message); + } catch (IOException e) { + callback.error(R.string.openpgp_error, message); + } - break; - case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: - callback.userInputRequried((PendingIntent) result - .getParcelableExtra(OpenPgpApi.RESULT_INTENT), - message); - break; - case OpenPgpApi.RESULT_CODE_ERROR: - logError(conversation.getAccount(), (OpenPgpError) result.getParcelableExtra(OpenPgpApi.RESULT_ERROR)); - callback.error(R.string.openpgp_error, message); - break; - } - } - }); - } else { - try { - DownloadableFile inputFile = this.mXmppConnectionService - .getFileBackend().getFile(message, true); - DownloadableFile outputFile = this.mXmppConnectionService - .getFileBackend().getFile(message, false); - outputFile.getParentFile().mkdirs(); - outputFile.createNewFile(); - final InputStream is = new FileInputStream(inputFile); - final OutputStream os = new FileOutputStream(outputFile); - api.executeApiAsync(params, is, os, new IOpenPgpCallback() { + break; + case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: + callback.userInputRequried((PendingIntent) result + .getParcelableExtra(OpenPgpApi.RESULT_INTENT), + message); + break; + case OpenPgpApi.RESULT_CODE_ERROR: + logError(conversation.getAccount(), (OpenPgpError) result.getParcelableExtra(OpenPgpApi.RESULT_ERROR)); + callback.error(R.string.openpgp_error, message); + break; + } + } + }); + } else { + try { + DownloadableFile inputFile = this.mXmppConnectionService + .getFileBackend().getFile(message, true); + DownloadableFile outputFile = this.mXmppConnectionService + .getFileBackend().getFile(message, false); + outputFile.getParentFile().mkdirs(); + outputFile.createNewFile(); + final InputStream is = new FileInputStream(inputFile); + final OutputStream os = new FileOutputStream(outputFile); + api.executeApiAsync(params, is, os, new IOpenPgpCallback() { - @Override - public void onReturn(Intent result) { - switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, - OpenPgpApi.RESULT_CODE_ERROR)) { - case OpenPgpApi.RESULT_CODE_SUCCESS: - try { - os.flush(); - } catch (IOException ignored) { - //ignored - } - FileBackend.close(os); - callback.success(message); - break; - case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: - callback.userInputRequried( - (PendingIntent) result - .getParcelableExtra(OpenPgpApi.RESULT_INTENT), - message); - break; - case OpenPgpApi.RESULT_CODE_ERROR: - logError(conversation.getAccount(), (OpenPgpError) result.getParcelableExtra(OpenPgpApi.RESULT_ERROR)); - callback.error(R.string.openpgp_error, message); - break; - } - } - }); - } catch (final IOException e) { - callback.error(R.string.openpgp_error, message); - } - } - } + @Override + public void onReturn(Intent result) { + switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, + OpenPgpApi.RESULT_CODE_ERROR)) { + case OpenPgpApi.RESULT_CODE_SUCCESS: + try { + os.flush(); + } catch (IOException ignored) { + //ignored + } + FileBackend.close(os); + callback.success(message); + break; + case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: + callback.userInputRequried( + (PendingIntent) result + .getParcelableExtra(OpenPgpApi.RESULT_INTENT), + message); + break; + case OpenPgpApi.RESULT_CODE_ERROR: + logError(conversation.getAccount(), (OpenPgpError) result.getParcelableExtra(OpenPgpApi.RESULT_ERROR)); + callback.error(R.string.openpgp_error, message); + break; + } + } + }); + } catch (final IOException e) { + callback.error(R.string.openpgp_error, message); + } + } + } - public long fetchKeyId(Account account, String status, String signature) { - if ((signature == null) || (api == null)) { - return 0; - } - if (status == null) { - status = ""; - } - final StringBuilder pgpSig = new StringBuilder(); - pgpSig.append("-----BEGIN PGP SIGNED MESSAGE-----"); - pgpSig.append('\n'); - pgpSig.append('\n'); - pgpSig.append(status); - pgpSig.append('\n'); - pgpSig.append("-----BEGIN PGP SIGNATURE-----"); - pgpSig.append('\n'); - pgpSig.append('\n'); - pgpSig.append(signature.replace("\n", "").trim()); - pgpSig.append('\n'); - pgpSig.append("-----END PGP SIGNATURE-----"); - Intent params = new Intent(); - params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY); - params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); - InputStream is = new ByteArrayInputStream(pgpSig.toString().getBytes()); - ByteArrayOutputStream os = new ByteArrayOutputStream(); - Intent result = api.executeApi(params, is, os); - switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, - OpenPgpApi.RESULT_CODE_ERROR)) { - case OpenPgpApi.RESULT_CODE_SUCCESS: - OpenPgpSignatureResult sigResult = result - .getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE); - if (sigResult != null) { - return sigResult.getKeyId(); - } else { - return 0; - } - case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: - return 0; - case OpenPgpApi.RESULT_CODE_ERROR: - logError(account, (OpenPgpError) result.getParcelableExtra(OpenPgpApi.RESULT_ERROR)); - return 0; - } - return 0; - } + public long fetchKeyId(Account account, String status, String signature) { + if ((signature == null) || (api == null)) { + return 0; + } + if (status == null) { + status = ""; + } + final StringBuilder pgpSig = new StringBuilder(); + pgpSig.append("-----BEGIN PGP SIGNED MESSAGE-----"); + pgpSig.append('\n'); + pgpSig.append('\n'); + pgpSig.append(status); + pgpSig.append('\n'); + pgpSig.append("-----BEGIN PGP SIGNATURE-----"); + pgpSig.append('\n'); + pgpSig.append('\n'); + pgpSig.append(signature.replace("\n", "").trim()); + pgpSig.append('\n'); + pgpSig.append("-----END PGP SIGNATURE-----"); + Intent params = new Intent(); + params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY); + params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); + InputStream is = new ByteArrayInputStream(pgpSig.toString().getBytes()); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + Intent result = api.executeApi(params, is, os); + switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, + OpenPgpApi.RESULT_CODE_ERROR)) { + case OpenPgpApi.RESULT_CODE_SUCCESS: + OpenPgpSignatureResult sigResult = result + .getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE); + if (sigResult != null) { + return sigResult.getKeyId(); + } else { + return 0; + } + case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: + return 0; + case OpenPgpApi.RESULT_CODE_ERROR: + logError(account, (OpenPgpError) result.getParcelableExtra(OpenPgpApi.RESULT_ERROR)); + return 0; + } + return 0; + } - public void chooseKey(final Account account, final UiCallback callback) { - Intent p = new Intent(); - p.setAction(OpenPgpApi.ACTION_GET_SIGN_KEY_ID); - api.executeApiAsync(p, null, null, new IOpenPgpCallback() { + public void chooseKey(final Account account, final UiCallback callback) { + Intent p = new Intent(); + p.setAction(OpenPgpApi.ACTION_GET_SIGN_KEY_ID); + api.executeApiAsync(p, null, null, new IOpenPgpCallback() { - @Override - public void onReturn(Intent result) { - switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) { - case OpenPgpApi.RESULT_CODE_SUCCESS: - callback.success(account); - return; - case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: - callback.userInputRequried((PendingIntent) result - .getParcelableExtra(OpenPgpApi.RESULT_INTENT), - account); - return; - case OpenPgpApi.RESULT_CODE_ERROR: - logError(account, (OpenPgpError) result.getParcelableExtra(OpenPgpApi.RESULT_ERROR)); - callback.error(R.string.openpgp_error, account); - } - } - }); - } + @Override + public void onReturn(Intent result) { + switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) { + case OpenPgpApi.RESULT_CODE_SUCCESS: + callback.success(account); + return; + case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: + callback.userInputRequried((PendingIntent) result + .getParcelableExtra(OpenPgpApi.RESULT_INTENT), + account); + return; + case OpenPgpApi.RESULT_CODE_ERROR: + logError(account, (OpenPgpError) result.getParcelableExtra(OpenPgpApi.RESULT_ERROR)); + callback.error(R.string.openpgp_error, account); + } + } + }); + } - public void generateSignature(final Account account, String status, - final UiCallback callback) { - if (account.getPgpId() == 0) { - return; - } - Intent params = new Intent(); - params.setAction(OpenPgpApi.ACTION_CLEARTEXT_SIGN); - params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); - params.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, account.getPgpId()); - InputStream is = new ByteArrayInputStream(status.getBytes()); - final OutputStream os = new ByteArrayOutputStream(); - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": signing status message \""+status+"\""); - api.executeApiAsync(params, is, os, new IOpenPgpCallback() { + public void generateSignature(final Account account, String status, + final UiCallback callback) { + if (account.getPgpId() == 0) { + return; + } + Intent params = new Intent(); + params.setAction(OpenPgpApi.ACTION_CLEARTEXT_SIGN); + params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); + params.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, account.getPgpId()); + InputStream is = new ByteArrayInputStream(status.getBytes()); + final OutputStream os = new ByteArrayOutputStream(); + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": signing status message \"" + status + "\""); + api.executeApiAsync(params, is, os, new IOpenPgpCallback() { - @Override - public void onReturn(Intent result) { - switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) { - case OpenPgpApi.RESULT_CODE_SUCCESS: - StringBuilder signatureBuilder = new StringBuilder(); - try { - os.flush(); - String[] lines = os.toString().split("\n"); - boolean sig = false; - for (String line : lines) { - if (sig) { - if (line.contains("END PGP SIGNATURE")) { - sig = false; - } else { - if (!line.contains("Version")) { - signatureBuilder.append(line.trim()); - } - } - } - if (line.contains("BEGIN PGP SIGNATURE")) { - sig = true; - } - } - } catch (IOException e) { - callback.error(R.string.openpgp_error, account); - return; - } - account.setPgpSignature(signatureBuilder.toString()); - callback.success(account); - return; - case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: - callback.userInputRequried((PendingIntent) result - .getParcelableExtra(OpenPgpApi.RESULT_INTENT), - account); - return; - case OpenPgpApi.RESULT_CODE_ERROR: - OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR); - if (error != null && "signing subkey not found!".equals(error.getMessage())) { - callback.error(0,account); - } else { - logError(account, error); - callback.error(R.string.unable_to_connect_to_keychain, null); - } + @Override + public void onReturn(Intent result) { + switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) { + case OpenPgpApi.RESULT_CODE_SUCCESS: + StringBuilder signatureBuilder = new StringBuilder(); + try { + os.flush(); + String[] lines = os.toString().split("\n"); + boolean sig = false; + for (String line : lines) { + if (sig) { + if (line.contains("END PGP SIGNATURE")) { + sig = false; + } else { + if (!line.contains("Version")) { + signatureBuilder.append(line.trim()); + } + } + } + if (line.contains("BEGIN PGP SIGNATURE")) { + sig = true; + } + } + } catch (IOException e) { + callback.error(R.string.openpgp_error, account); + return; + } + account.setPgpSignature(signatureBuilder.toString()); + callback.success(account); + return; + case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: + callback.userInputRequried((PendingIntent) result + .getParcelableExtra(OpenPgpApi.RESULT_INTENT), + account); + return; + case OpenPgpApi.RESULT_CODE_ERROR: + OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR); + if (error != null && "signing subkey not found!".equals(error.getMessage())) { + callback.error(0, account); + } else { + logError(account, error); + callback.error(R.string.unable_to_connect_to_keychain, null); + } } - } - }); - } + } + }); + } - public void hasKey(final Contact contact, final UiCallback callback) { - Intent params = new Intent(); - params.setAction(OpenPgpApi.ACTION_GET_KEY); - params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId()); - api.executeApiAsync(params, null, null, new IOpenPgpCallback() { + public void hasKey(final Contact contact, final UiCallback callback) { + Intent params = new Intent(); + params.setAction(OpenPgpApi.ACTION_GET_KEY); + params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId()); + api.executeApiAsync(params, null, null, new IOpenPgpCallback() { - @Override - public void onReturn(Intent result) { - switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) { - case OpenPgpApi.RESULT_CODE_SUCCESS: - callback.success(contact); - return; - case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: - callback.userInputRequried((PendingIntent) result - .getParcelableExtra(OpenPgpApi.RESULT_INTENT), - contact); - return; - case OpenPgpApi.RESULT_CODE_ERROR: - logError(contact.getAccount(), (OpenPgpError) result.getParcelableExtra(OpenPgpApi.RESULT_ERROR)); - callback.error(R.string.openpgp_error, contact); + @Override + public void onReturn(Intent result) { + switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) { + case OpenPgpApi.RESULT_CODE_SUCCESS: + callback.success(contact); + return; + case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: + callback.userInputRequried((PendingIntent) result + .getParcelableExtra(OpenPgpApi.RESULT_INTENT), + contact); + return; + case OpenPgpApi.RESULT_CODE_ERROR: + logError(contact.getAccount(), (OpenPgpError) result.getParcelableExtra(OpenPgpApi.RESULT_ERROR)); + callback.error(R.string.openpgp_error, contact); } - } - }); - } + } + }); + } - private static void logError(Account account, OpenPgpError error) { - if (error != null) { - Log.d(Config.LOGTAG,account.getJid().toBareJid().toString()+": OpenKeychain error '"+error.getMessage()+"' code="+error.getErrorId()); - } else { - Log.d(Config.LOGTAG,account.getJid().toBareJid().toString()+": OpenKeychain error with no message"); - } - } + private static void logError(Account account, OpenPgpError error) { + if (error != null) { + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": OpenKeychain error '" + error.getMessage() + "' code=" + error.getErrorId()); + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": OpenKeychain error with no message"); + } + } - public PendingIntent getIntentForKey(Contact contact) { - return getIntentForKey(contact.getPgpKeyId()); - } + public PendingIntent getIntentForKey(Contact contact) { + return getIntentForKey(contact.getPgpKeyId()); + } - public PendingIntent getIntentForKey(long pgpKeyId) { - Intent params = new Intent(); - params.setAction(OpenPgpApi.ACTION_GET_KEY); - params.putExtra(OpenPgpApi.EXTRA_KEY_ID, pgpKeyId); - Intent result = api.executeApi(params, null, null); - return (PendingIntent) result.getParcelableExtra(OpenPgpApi.RESULT_INTENT); - } + public PendingIntent getIntentForKey(long pgpKeyId) { + Intent params = new Intent(); + params.setAction(OpenPgpApi.ACTION_GET_KEY); + params.putExtra(OpenPgpApi.EXTRA_KEY_ID, pgpKeyId); + Intent result = api.executeApi(params, null, null); + return (PendingIntent) result.getParcelableExtra(OpenPgpApi.RESULT_INTENT); + } } diff --git a/src/main/java/de/pixart/messenger/crypto/XmppDomainVerifier.java b/src/main/java/de/pixart/messenger/crypto/XmppDomainVerifier.java index 250516daa..d305a33d5 100644 --- a/src/main/java/de/pixart/messenger/crypto/XmppDomainVerifier.java +++ b/src/main/java/de/pixart/messenger/crypto/XmppDomainVerifier.java @@ -26,102 +26,102 @@ import javax.net.ssl.SSLSession; public class XmppDomainVerifier implements HostnameVerifier { - private static final String LOGTAG = "XmppDomainVerifier"; + private static final String LOGTAG = "XmppDomainVerifier"; - private final String SRVName = "1.3.6.1.5.5.7.8.7"; - private final String xmppAddr = "1.3.6.1.5.5.7.8.5"; + private final String SRVName = "1.3.6.1.5.5.7.8.7"; + private final String xmppAddr = "1.3.6.1.5.5.7.8.5"; - @Override - public boolean verify(String domain, SSLSession sslSession) { - try { - Certificate[] chain = sslSession.getPeerCertificates(); - if (chain.length == 0 || !(chain[0] instanceof X509Certificate)) { - return false; - } - X509Certificate certificate = (X509Certificate) chain[0]; - Collection> alternativeNames = certificate.getSubjectAlternativeNames(); - List xmppAddrs = new ArrayList<>(); - List srvNames = new ArrayList<>(); - List domains = new ArrayList<>(); - if (alternativeNames != null) { - for (List san : alternativeNames) { - Integer type = (Integer) san.get(0); - if (type == 0) { - Pair otherName = parseOtherName((byte[]) san.get(1)); - if (otherName != null) { - switch (otherName.first) { - case SRVName: - srvNames.add(otherName.second); - break; - case xmppAddr: - xmppAddrs.add(otherName.second); - break; - default: - Log.d(LOGTAG, "oid: " + otherName.first + " value: " + otherName.second); - } - } - } else if (type == 2) { - Object value = san.get(1); - if (value instanceof String) { - domains.add((String) value); - } - } - } - } - if (srvNames.size() == 0 && xmppAddrs.size() == 0 && domains.size() == 0) { - X500Name x500name = new JcaX509CertificateHolder(certificate).getSubject(); - RDN[] rdns = x500name.getRDNs(BCStyle.CN); - for (int i = 0; i < rdns.length; ++i) { - domains.add(IETFUtils.valueToString(x500name.getRDNs(BCStyle.CN)[i].getFirst().getValue())); - } - } - Log.d(LOGTAG, "searching for " + domain + " in srvNames: " + srvNames + " xmppAddrs: " + xmppAddrs + " domains:" + domains); - return xmppAddrs.contains(domain) || srvNames.contains("_xmpp-client." + domain) || matchDomain(domain, domains); - } catch (Exception e) { - return false; - } - } + @Override + public boolean verify(String domain, SSLSession sslSession) { + try { + Certificate[] chain = sslSession.getPeerCertificates(); + if (chain.length == 0 || !(chain[0] instanceof X509Certificate)) { + return false; + } + X509Certificate certificate = (X509Certificate) chain[0]; + Collection> alternativeNames = certificate.getSubjectAlternativeNames(); + List xmppAddrs = new ArrayList<>(); + List srvNames = new ArrayList<>(); + List domains = new ArrayList<>(); + if (alternativeNames != null) { + for (List san : alternativeNames) { + Integer type = (Integer) san.get(0); + if (type == 0) { + Pair otherName = parseOtherName((byte[]) san.get(1)); + if (otherName != null) { + switch (otherName.first) { + case SRVName: + srvNames.add(otherName.second); + break; + case xmppAddr: + xmppAddrs.add(otherName.second); + break; + default: + Log.d(LOGTAG, "oid: " + otherName.first + " value: " + otherName.second); + } + } + } else if (type == 2) { + Object value = san.get(1); + if (value instanceof String) { + domains.add((String) value); + } + } + } + } + if (srvNames.size() == 0 && xmppAddrs.size() == 0 && domains.size() == 0) { + X500Name x500name = new JcaX509CertificateHolder(certificate).getSubject(); + RDN[] rdns = x500name.getRDNs(BCStyle.CN); + for (int i = 0; i < rdns.length; ++i) { + domains.add(IETFUtils.valueToString(x500name.getRDNs(BCStyle.CN)[i].getFirst().getValue())); + } + } + Log.d(LOGTAG, "searching for " + domain + " in srvNames: " + srvNames + " xmppAddrs: " + xmppAddrs + " domains:" + domains); + return xmppAddrs.contains(domain) || srvNames.contains("_xmpp-client." + domain) || matchDomain(domain, domains); + } catch (Exception e) { + return false; + } + } - private static Pair parseOtherName(byte[] otherName) { - try { - ASN1Primitive asn1Primitive = ASN1Primitive.fromByteArray(otherName); - if (asn1Primitive instanceof DERTaggedObject) { - ASN1Primitive inner = ((DERTaggedObject) asn1Primitive).getObject(); - if (inner instanceof DLSequence) { - DLSequence sequence = (DLSequence) inner; - if (sequence.size() >= 2 && sequence.getObjectAt(1) instanceof DERTaggedObject) { - String oid = sequence.getObjectAt(0).toString(); - ASN1Primitive value = ((DERTaggedObject) sequence.getObjectAt(1)).getObject(); - if (value instanceof DERUTF8String) { - return new Pair<>(oid, ((DERUTF8String) value).getString()); - } else if (value instanceof DERIA5String) { - return new Pair<>(oid, ((DERIA5String) value).getString()); - } - } - } - } - return null; - } catch (IOException e) { - return null; - } - } + private static Pair parseOtherName(byte[] otherName) { + try { + ASN1Primitive asn1Primitive = ASN1Primitive.fromByteArray(otherName); + if (asn1Primitive instanceof DERTaggedObject) { + ASN1Primitive inner = ((DERTaggedObject) asn1Primitive).getObject(); + if (inner instanceof DLSequence) { + DLSequence sequence = (DLSequence) inner; + if (sequence.size() >= 2 && sequence.getObjectAt(1) instanceof DERTaggedObject) { + String oid = sequence.getObjectAt(0).toString(); + ASN1Primitive value = ((DERTaggedObject) sequence.getObjectAt(1)).getObject(); + if (value instanceof DERUTF8String) { + return new Pair<>(oid, ((DERUTF8String) value).getString()); + } else if (value instanceof DERIA5String) { + return new Pair<>(oid, ((DERIA5String) value).getString()); + } + } + } + } + return null; + } catch (IOException e) { + return null; + } + } - private static boolean matchDomain(String needle, List haystack) { - for (String entry : haystack) { - if (entry.startsWith("*.")) { - int i = needle.indexOf('.'); - Log.d(LOGTAG, "comparing " + needle.substring(i) + " and " + entry.substring(1)); - if (i != -1 && needle.substring(i).equals(entry.substring(1))) { - Log.d(LOGTAG, "domain " + needle + " matched " + entry); - return true; - } - } else { - if (entry.equals(needle)) { - Log.d(LOGTAG, "domain " + needle + " matched " + entry); - return true; - } - } - } - return false; - } + private static boolean matchDomain(String needle, List haystack) { + for (String entry : haystack) { + if (entry.startsWith("*.")) { + int i = needle.indexOf('.'); + Log.d(LOGTAG, "comparing " + needle.substring(i) + " and " + entry.substring(1)); + if (i != -1 && needle.substring(i).equals(entry.substring(1))) { + Log.d(LOGTAG, "domain " + needle + " matched " + entry); + return true; + } + } else { + if (entry.equals(needle)) { + Log.d(LOGTAG, "domain " + needle + " matched " + entry); + return true; + } + } + } + return false; + } } diff --git a/src/main/java/de/pixart/messenger/crypto/axolotl/AxolotlService.java b/src/main/java/de/pixart/messenger/crypto/axolotl/AxolotlService.java index 532fbe470..1ed410fdb 100644 --- a/src/main/java/de/pixart/messenger/crypto/axolotl/AxolotlService.java +++ b/src/main/java/de/pixart/messenger/crypto/axolotl/AxolotlService.java @@ -51,1049 +51,1049 @@ import de.pixart.messenger.xmpp.stanzas.IqPacket; public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { - public static final String PEP_PREFIX = "eu.siacs.conversations.axolotl"; - public static final String PEP_DEVICE_LIST = PEP_PREFIX + ".devicelist"; - public static final String PEP_DEVICE_LIST_NOTIFY = PEP_DEVICE_LIST + "+notify"; - public static final String PEP_BUNDLES = PEP_PREFIX + ".bundles"; - public static final String PEP_VERIFICATION = PEP_PREFIX + ".verification"; - - public static final String LOGPREFIX = "AxolotlService"; - - public static final int NUM_KEYS_TO_PUBLISH = 100; - public static final int publishTriesThreshold = 3; - - private final Account account; - private final XmppConnectionService mXmppConnectionService; - private final SQLiteAxolotlStore axolotlStore; - private final SessionMap sessions; - private final Map> deviceIds; - private final Map messageCache; - private final FetchStatusMap fetchStatusMap; - private final SerialSingleThreadExecutor executor; - private int numPublishTriesOnEmptyPep = 0; - private boolean pepBroken = false; - - @Override - public void onAdvancedStreamFeaturesAvailable(Account account) { - if (Config.supportOmemo() - && account.getXmppConnection() != null - && account.getXmppConnection().getFeatures().pep()) { - publishBundlesIfNeeded(true, false); - } else { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": skipping OMEMO initialization"); - } - } - - public boolean fetchMapHasErrors(List jids) { - for(Jid jid : jids) { - if (deviceIds.get(jid) != null) { - for (Integer foreignId : this.deviceIds.get(jid)) { - AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId); - if (fetchStatusMap.getAll(address).containsValue(FetchStatus.ERROR)) { - return true; - } - } - } - } - return false; - } - - public void preVerifyFingerprint(Contact contact, String fingerprint) { - axolotlStore.preVerifyFingerprint(contact.getAccount(), contact.getJid().toBareJid().toPreppedString(), fingerprint); - } - - public void preVerifyFingerprint(Account account, String fingerprint) { - axolotlStore.preVerifyFingerprint(account, account.getJid().toBareJid().toPreppedString(), fingerprint); - } - - private static class AxolotlAddressMap { - protected Map> map; - protected final Object MAP_LOCK = new Object(); - - public AxolotlAddressMap() { - this.map = new HashMap<>(); - } - - public void put(AxolotlAddress address, T value) { - synchronized (MAP_LOCK) { - Map devices = map.get(address.getName()); - if (devices == null) { - devices = new HashMap<>(); - map.put(address.getName(), devices); - } - devices.put(address.getDeviceId(), value); - } - } - - public T get(AxolotlAddress address) { - synchronized (MAP_LOCK) { - Map devices = map.get(address.getName()); - if (devices == null) { - return null; - } - return devices.get(address.getDeviceId()); - } - } - - public Map getAll(AxolotlAddress address) { - synchronized (MAP_LOCK) { - Map devices = map.get(address.getName()); - if (devices == null) { - return new HashMap<>(); - } - return devices; - } - } - - public boolean hasAny(AxolotlAddress address) { - synchronized (MAP_LOCK) { - Map devices = map.get(address.getName()); - return devices != null && !devices.isEmpty(); - } - } - - public void clear() { - map.clear(); - } - - } - - private static class SessionMap extends AxolotlAddressMap { - private final XmppConnectionService xmppConnectionService; - private final Account account; - - public SessionMap(XmppConnectionService service, SQLiteAxolotlStore store, Account account) { - super(); - this.xmppConnectionService = service; - this.account = account; - this.fillMap(store); - } - - private void putDevicesForJid(String bareJid, List deviceIds, SQLiteAxolotlStore store) { - for (Integer deviceId : deviceIds) { - AxolotlAddress axolotlAddress = new AxolotlAddress(bareJid, deviceId); - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building session for remote address: " + axolotlAddress.toString()); - IdentityKey identityKey = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey(); - if(Config.X509_VERIFICATION) { - X509Certificate certificate = store.getFingerprintCertificate(identityKey.getFingerprint().replaceAll("\\s", "")); - if (certificate != null) { - Bundle information = CryptoHelper.extractCertificateInformation(certificate); - try { - final String cn = information.getString("subject_cn"); - final Jid jid = Jid.fromString(bareJid); - Log.d(Config.LOGTAG,"setting common name for "+jid+" to "+cn); - account.getRoster().getContact(jid).setCommonName(cn); - } catch (final InvalidJidException ignored) { - //ignored - } - } - } - this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress, identityKey)); - } - } - - private void fillMap(SQLiteAxolotlStore store) { - List deviceIds = store.getSubDeviceSessions(account.getJid().toBareJid().toPreppedString()); - putDevicesForJid(account.getJid().toBareJid().toPreppedString(), deviceIds, store); - for (Contact contact : account.getRoster().getContacts()) { - Jid bareJid = contact.getJid().toBareJid(); - String address = bareJid.toString(); - deviceIds = store.getSubDeviceSessions(address); - putDevicesForJid(address, deviceIds, store); - } - - } - - @Override - public void put(AxolotlAddress address, XmppAxolotlSession value) { - super.put(address, value); - value.setNotFresh(); - xmppConnectionService.syncRosterToDisk(account); //TODO why? - } - - public void put(XmppAxolotlSession session) { - this.put(session.getRemoteAddress(), session); - } - } - - public enum FetchStatus { - PENDING, - SUCCESS, - SUCCESS_VERIFIED, - TIMEOUT, - ERROR - } - - private static class FetchStatusMap extends AxolotlAddressMap { - - public void clearErrorFor(Jid jid) { - synchronized (MAP_LOCK) { - Map devices = this.map.get(jid.toBareJid().toPreppedString()); - if (devices == null) { - return; - } - for(Map.Entry entry : devices.entrySet()) { - if (entry.getValue() == FetchStatus.ERROR) { - Log.d(Config.LOGTAG,"resetting error for "+jid.toBareJid()+"("+entry.getKey()+")"); - entry.setValue(FetchStatus.TIMEOUT); - } - } - } - } - } - - public static String getLogprefix(Account account) { - return LOGPREFIX + " (" + account.getJid().toBareJid().toString() + "): "; - } - - public AxolotlService(Account account, XmppConnectionService connectionService) { - if (Security.getProvider("BC") == null) { - Security.addProvider(new BouncyCastleProvider()); - } - this.mXmppConnectionService = connectionService; - this.account = account; - this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService); - this.deviceIds = new HashMap<>(); - this.messageCache = new HashMap<>(); - this.sessions = new SessionMap(mXmppConnectionService, axolotlStore, account); - this.fetchStatusMap = new FetchStatusMap(); - this.executor = new SerialSingleThreadExecutor(); - } - - public String getOwnFingerprint() { - return axolotlStore.getIdentityKeyPair().getPublicKey().getFingerprint().replaceAll("\\s", ""); - } - - public Set getKeysWithTrust(FingerprintStatus status) { - return axolotlStore.getContactKeysWithTrust(account.getJid().toBareJid().toPreppedString(), status); - } - - public Set getKeysWithTrust(FingerprintStatus status, Jid jid) { - return axolotlStore.getContactKeysWithTrust(jid.toBareJid().toPreppedString(), status); - } - - public Set getKeysWithTrust(FingerprintStatus status, List jids) { - Set keys = new HashSet<>(); - for(Jid jid : jids) { - keys.addAll(axolotlStore.getContactKeysWithTrust(jid.toPreppedString(), status)); - } - return keys; - } - - public long getNumTrustedKeys(Jid jid) { - return axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toPreppedString()); - } - - public boolean anyTargetHasNoTrustedKeys(List jids) { - for(Jid jid : jids) { - if (axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toPreppedString()) == 0) { - return true; - } - } - return false; - } - - private AxolotlAddress getAddressForJid(Jid jid) { - return new AxolotlAddress(jid.toPreppedString(), 0); - } - - public Set findOwnSessions() { - AxolotlAddress ownAddress = getAddressForJid(account.getJid().toBareJid()); - return new HashSet<>(this.sessions.getAll(ownAddress).values()); - } - - private Set findSessionsForContact(Contact contact) { - AxolotlAddress contactAddress = getAddressForJid(contact.getJid()); - return new HashSet<>(this.sessions.getAll(contactAddress).values()); - } - - private Set findSessionsForConversation(Conversation conversation) { - HashSet sessions = new HashSet<>(); - for(Jid jid : conversation.getAcceptedCryptoTargets()) { - sessions.addAll(this.sessions.getAll(getAddressForJid(jid)).values()); - } - return sessions; - } - - public Set getFingerprintsForOwnSessions() { - Set fingerprints = new HashSet<>(); - for (XmppAxolotlSession session : findOwnSessions()) { - fingerprints.add(session.getFingerprint()); - } - return fingerprints; - } - - public Set getFingerprintsForContact(final Contact contact) { - Set fingerprints = new HashSet<>(); - for (XmppAxolotlSession session : findSessionsForContact(contact)) { - fingerprints.add(session.getFingerprint()); - } - return fingerprints; - } - - private boolean hasAny(Jid jid) { - return sessions.hasAny(getAddressForJid(jid)); - } - - public boolean isPepBroken() { - return this.pepBroken; - } - - public void resetBrokenness() { - this.pepBroken = false; - numPublishTriesOnEmptyPep = 0; - } - - public void clearErrorsInFetchStatusMap(Jid jid) { - fetchStatusMap.clearErrorFor(jid); - } - - public void regenerateKeys(boolean wipeOther) { - axolotlStore.regenerate(); - sessions.clear(); - fetchStatusMap.clear(); - publishBundlesIfNeeded(true, wipeOther); - } - - public int getOwnDeviceId() { - return axolotlStore.getLocalRegistrationId(); - } - - public Set getOwnDeviceIds() { - return this.deviceIds.get(account.getJid().toBareJid()); - } - - public void registerDevices(final Jid jid, @NonNull final Set deviceIds) { - if (jid.toBareJid().equals(account.getJid().toBareJid())) { - if (!deviceIds.isEmpty()) { - Log.d(Config.LOGTAG, getLogprefix(account) + "Received non-empty own device list. Resetting publish attempts and pepBroken status."); - pepBroken = false; - numPublishTriesOnEmptyPep = 0; - } - if (deviceIds.contains(getOwnDeviceId())) { - deviceIds.remove(getOwnDeviceId()); - } else { - publishOwnDeviceId(deviceIds); - } - for (Integer deviceId : deviceIds) { - AxolotlAddress ownDeviceAddress = new AxolotlAddress(jid.toBareJid().toPreppedString(), deviceId); - if (sessions.get(ownDeviceAddress) == null) { - buildSessionFromPEP(ownDeviceAddress); - } - } - } - Set expiredDevices = new HashSet<>(axolotlStore.getSubDeviceSessions(jid.toBareJid().toPreppedString())); - expiredDevices.removeAll(deviceIds); - for (Integer deviceId : expiredDevices) { - AxolotlAddress address = new AxolotlAddress(jid.toBareJid().toPreppedString(), deviceId); - XmppAxolotlSession session = sessions.get(address); - if (session != null && session.getFingerprint() != null) { - if (session.getTrust().isActive()) { - session.setTrust(session.getTrust().toInactive()); - } - } - } - Set newDevices = new HashSet<>(deviceIds); - for (Integer deviceId : newDevices) { - AxolotlAddress address = new AxolotlAddress(jid.toBareJid().toPreppedString(), deviceId); - XmppAxolotlSession session = sessions.get(address); - if (session != null && session.getFingerprint() != null) { - if (!session.getTrust().isActive()) { - session.setTrust(session.getTrust().toActive()); - } - } - } - this.deviceIds.put(jid, deviceIds); - mXmppConnectionService.keyStatusUpdated(null); - } - - public void wipeOtherPepDevices() { - if (pepBroken) { - Log.d(Config.LOGTAG, getLogprefix(account) + "wipeOtherPepDevices called, but PEP is broken. Ignoring... "); - return; - } - Set deviceIds = new HashSet<>(); - deviceIds.add(getOwnDeviceId()); - IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds); - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Wiping all other devices from Pep:" + publish); - mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - // TODO: implement this! - } - }); - } - - public void purgeKey(final String fingerprint) { - axolotlStore.setFingerprintStatus(fingerprint.replaceAll("\\s", ""), FingerprintStatus.createCompromised()); - } - - public void publishOwnDeviceIdIfNeeded() { - if (pepBroken) { - Log.d(Config.LOGTAG, getLogprefix(account) + "publishOwnDeviceIdIfNeeded called, but PEP is broken. Ignoring... "); - return; - } - IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().toBareJid()); - mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.TIMEOUT) { - Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids."); - } else { - Element item = mXmppConnectionService.getIqParser().getItem(packet); - Set deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); - if (!deviceIds.contains(getOwnDeviceId())) { - publishOwnDeviceId(deviceIds); - } - } - } - }); - } - - public void publishOwnDeviceId(Set deviceIds) { - Set deviceIdsCopy = new HashSet<>(deviceIds); - if (!deviceIdsCopy.contains(getOwnDeviceId())) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Own device " + getOwnDeviceId() + " not in PEP devicelist."); - if (deviceIdsCopy.isEmpty()) { - if (numPublishTriesOnEmptyPep >= publishTriesThreshold) { - Log.w(Config.LOGTAG, getLogprefix(account) + "Own device publish attempt threshold exceeded, aborting..."); - pepBroken = true; - return; - } else { - numPublishTriesOnEmptyPep++; - Log.w(Config.LOGTAG, getLogprefix(account) + "Own device list empty, attempting to publish (try " + numPublishTriesOnEmptyPep + ")"); - } - } else { - numPublishTriesOnEmptyPep = 0; - } - deviceIdsCopy.add(getOwnDeviceId()); - IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIdsCopy); - mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.ERROR) { - pepBroken = true; - Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + packet.findChild("error")); - } - } - }); - } - } - - public void publishDeviceVerificationAndBundle(final SignedPreKeyRecord signedPreKeyRecord, - final Set preKeyRecords, - final boolean announceAfter, - final boolean wipe) { - try { - IdentityKey axolotlPublicKey = axolotlStore.getIdentityKeyPair().getPublicKey(); - PrivateKey x509PrivateKey = KeyChain.getPrivateKey(mXmppConnectionService, account.getPrivateKeyAlias()); - X509Certificate[] chain = KeyChain.getCertificateChain(mXmppConnectionService, account.getPrivateKeyAlias()); - Signature verifier = Signature.getInstance("sha256WithRSA"); - verifier.initSign(x509PrivateKey,mXmppConnectionService.getRNG()); - verifier.update(axolotlPublicKey.serialize()); - byte[] signature = verifier.sign(); - IqPacket packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId()); - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": publish verification for device "+getOwnDeviceId()); - mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe); - } - }); - } catch (Exception e) { - e.printStackTrace(); - } - } - - public void publishBundlesIfNeeded(final boolean announce, final boolean wipe) { - if (pepBroken) { - Log.d(Config.LOGTAG, getLogprefix(account) + "publishBundlesIfNeeded called, but PEP is broken. Ignoring... "); - return; - } - IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().toBareJid(), getOwnDeviceId()); - mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - - if (packet.getType() == IqPacket.TYPE.TIMEOUT) { - return; //ignore timeout. do nothing - } - - if (packet.getType() == IqPacket.TYPE.ERROR) { - Element error = packet.findChild("error"); - if (error == null || !error.hasChild("item-not-found")) { - pepBroken = true; - Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "request for device bundles came back with something other than item-not-found" + packet); - return; - } - } - - PreKeyBundle bundle = mXmppConnectionService.getIqParser().bundle(packet); - Map keys = mXmppConnectionService.getIqParser().preKeyPublics(packet); - boolean flush = false; - if (bundle == null) { - Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid bundle:" + packet); - bundle = new PreKeyBundle(-1, -1, -1, null, -1, null, null, null); - flush = true; - } - if (keys == null) { - Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid prekeys:" + packet); - } - try { - boolean changed = false; - // Validate IdentityKey - IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair(); - if (flush || !identityKeyPair.getPublicKey().equals(bundle.getIdentityKey())) { - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP."); - changed = true; - } - - // Validate signedPreKeyRecord + ID - SignedPreKeyRecord signedPreKeyRecord; - int numSignedPreKeys = axolotlStore.loadSignedPreKeys().size(); - try { - signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId()); - if (flush - || !bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey()) - || !Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) { - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP."); - signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1); - axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord); - changed = true; - } - } catch (InvalidKeyIdException e) { - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP."); - signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1); - axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord); - changed = true; - } - - // Validate PreKeys - Set preKeyRecords = new HashSet<>(); - if (keys != null) { - for (Integer id : keys.keySet()) { - try { - PreKeyRecord preKeyRecord = axolotlStore.loadPreKey(id); - if (preKeyRecord.getKeyPair().getPublicKey().equals(keys.get(id))) { - preKeyRecords.add(preKeyRecord); - } - } catch (InvalidKeyIdException ignored) { - } - } - } - int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords.size(); - if (newKeys > 0) { - List newRecords = KeyHelper.generatePreKeys( - axolotlStore.getCurrentPreKeyId() + 1, newKeys); - preKeyRecords.addAll(newRecords); - for (PreKeyRecord record : newRecords) { - axolotlStore.storePreKey(record.getId(), record); - } - changed = true; - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding " + newKeys + " new preKeys to PEP."); - } - - - if (changed) { - if (account.getPrivateKeyAlias() != null && Config.X509_VERIFICATION) { - mXmppConnectionService.publishDisplayName(account); - publishDeviceVerificationAndBundle(signedPreKeyRecord, preKeyRecords, announce, wipe); - } else { - publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announce, wipe); - } - } else { - Log.d(Config.LOGTAG, getLogprefix(account) + "Bundle " + getOwnDeviceId() + " in PEP was current"); - if (wipe) { - wipeOtherPepDevices(); - } else if (announce) { - Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId()); - publishOwnDeviceIdIfNeeded(); - } - } - } catch (InvalidKeyException e) { - Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage()); - } - } - }); - } - - private void publishDeviceBundle(SignedPreKeyRecord signedPreKeyRecord, - Set preKeyRecords, - final boolean announceAfter, - final boolean wipe) { - IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles( - signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(), - preKeyRecords, getOwnDeviceId()); - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing: " + publish); - mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Successfully published bundle. "); - if (wipe) { - wipeOtherPepDevices(); - } else if (announceAfter) { - Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId()); - publishOwnDeviceIdIfNeeded(); - } - } else if (packet.getType() == IqPacket.TYPE.ERROR) { - pepBroken = true; - Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " + packet.findChild("error")); - } - } - }); - } - - public enum AxolotlCapability { - FULL, - MISSING_PRESENCE, - MISSING_KEYS, - WRONG_CONFIGURATION, - NO_MEMBERS - } - - public boolean isConversationAxolotlCapable(Conversation conversation) { - return isConversationAxolotlCapableDetailed(conversation).first == AxolotlCapability.FULL; - } - - public Pair isConversationAxolotlCapableDetailed(Conversation conversation) { - if (conversation.getMode() == Conversation.MODE_SINGLE || (conversation.getMucOptions().membersOnly() && conversation.getMucOptions().nonanonymous())) { - final List jids = getCryptoTargets(conversation); - for(Jid jid : jids) { - if (!hasAny(jid) && (!deviceIds.containsKey(jid) || deviceIds.get(jid).isEmpty())) { - if (conversation.getAccount().getRoster().getContact(jid).mutualPresenceSubscription()) { - return new Pair<>(AxolotlCapability.MISSING_KEYS,jid); - } else { - return new Pair<>(AxolotlCapability.MISSING_PRESENCE,jid); - } - } - } - if (jids.size() > 0) { - return new Pair<>(AxolotlCapability.FULL, null); - } else { - return new Pair<>(AxolotlCapability.NO_MEMBERS, null); - } - } else { - return new Pair<>(AxolotlCapability.WRONG_CONFIGURATION, null); - } - } - - public List getCryptoTargets(Conversation conversation) { - final List jids; - if (conversation.getMode() == Conversation.MODE_SINGLE) { - jids = Arrays.asList(conversation.getJid().toBareJid()); - } else { - jids = conversation.getMucOptions().getMembers(); - } - return jids; - } - - public FingerprintStatus getFingerprintTrust(String fingerprint) { - return axolotlStore.getFingerprintStatus(fingerprint); - } - - public X509Certificate getFingerprintCertificate(String fingerprint) { - return axolotlStore.getFingerprintCertificate(fingerprint); - } - - public void setFingerprintTrust(String fingerprint, FingerprintStatus status) { - axolotlStore.setFingerprintStatus(fingerprint, status); - } - - private void verifySessionWithPEP(final XmppAxolotlSession session) { - Log.d(Config.LOGTAG, "trying to verify fresh session (" + session.getRemoteAddress().getName() + ") with pep"); - final AxolotlAddress address = session.getRemoteAddress(); - final IdentityKey identityKey = session.getIdentityKey(); - try { - IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(Jid.fromString(address.getName()), address.getDeviceId()); - mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - Pair verification = mXmppConnectionService.getIqParser().verification(packet); - if (verification != null) { - try { - Signature verifier = Signature.getInstance("sha256WithRSA"); - verifier.initVerify(verification.first[0]); - verifier.update(identityKey.serialize()); - if (verifier.verify(verification.second)) { - try { - mXmppConnectionService.getMemorizingTrustManager().getNonInteractive().checkClientTrusted(verification.first, "RSA"); - String fingerprint = session.getFingerprint(); - Log.d(Config.LOGTAG, "verified session with x.509 signature. fingerprint was: "+fingerprint); - setFingerprintTrust(fingerprint, FingerprintStatus.createActiveVerified(true)); - axolotlStore.setFingerprintCertificate(fingerprint, verification.first[0]); - fetchStatusMap.put(address, FetchStatus.SUCCESS_VERIFIED); - Bundle information = CryptoHelper.extractCertificateInformation(verification.first[0]); - try { - final String cn = information.getString("subject_cn"); - final Jid jid = Jid.fromString(address.getName()); - Log.d(Config.LOGTAG,"setting common name for "+jid+" to "+cn); - account.getRoster().getContact(jid).setCommonName(cn); - } catch (final InvalidJidException ignored) { - //ignored - } - finishBuildingSessionsFromPEP(address); - return; - } catch (Exception e) { - Log.d(Config.LOGTAG,"could not verify certificate"); - } - } - } catch (Exception e) { - Log.d(Config.LOGTAG, "error during verification " + e.getMessage()); - } - } else { - Log.d(Config.LOGTAG,"no verification found"); - } - fetchStatusMap.put(address, FetchStatus.SUCCESS); - finishBuildingSessionsFromPEP(address); - } - }); - } catch (InvalidJidException e) { - fetchStatusMap.put(address, FetchStatus.SUCCESS); - finishBuildingSessionsFromPEP(address); - } - } - - private void finishBuildingSessionsFromPEP(final AxolotlAddress address) { - AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), 0); - Map own = fetchStatusMap.getAll(ownAddress); - Map remote = fetchStatusMap.getAll(address); - if (!own.containsValue(FetchStatus.PENDING) && !remote.containsValue(FetchStatus.PENDING)) { - FetchStatus report = null; - if (own.containsValue(FetchStatus.SUCCESS) || remote.containsValue(FetchStatus.SUCCESS)) { - report = FetchStatus.SUCCESS; - } else if (own.containsValue(FetchStatus.SUCCESS_VERIFIED) || remote.containsValue(FetchStatus.SUCCESS_VERIFIED)) { - report = FetchStatus.SUCCESS_VERIFIED; - } else if (own.containsValue(FetchStatus.ERROR) || remote.containsValue(FetchStatus.ERROR)) { - report = FetchStatus.ERROR; - } - mXmppConnectionService.keyStatusUpdated(report); - } - } - - private void buildSessionFromPEP(final AxolotlAddress address) { - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new sesstion for " + address.toString()); - if (address.getDeviceId() == getOwnDeviceId()) { - throw new AssertionError("We should NEVER build a session with ourselves. What happened here?!"); - } - - try { - IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice( - Jid.fromString(address.getName()), address.getDeviceId()); - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Retrieving bundle: " + bundlesPacket); - mXmppConnectionService.sendIqPacket(account, bundlesPacket, new OnIqPacketReceived() { - - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.TIMEOUT) { - fetchStatusMap.put(address, FetchStatus.TIMEOUT); - } else if (packet.getType() == IqPacket.TYPE.RESULT) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing..."); - final IqParser parser = mXmppConnectionService.getIqParser(); - final List preKeyBundleList = parser.preKeys(packet); - final PreKeyBundle bundle = parser.bundle(packet); - if (preKeyBundleList.isEmpty() || bundle == null) { - Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet); - fetchStatusMap.put(address, FetchStatus.ERROR); - finishBuildingSessionsFromPEP(address); - return; - } - Random random = new Random(); - final PreKeyBundle preKey = preKeyBundleList.get(random.nextInt(preKeyBundleList.size())); - if (preKey == null) { - //should never happen - fetchStatusMap.put(address, FetchStatus.ERROR); - finishBuildingSessionsFromPEP(address); - return; - } - - final PreKeyBundle preKeyBundle = new PreKeyBundle(0, address.getDeviceId(), - preKey.getPreKeyId(), preKey.getPreKey(), - bundle.getSignedPreKeyId(), bundle.getSignedPreKey(), - bundle.getSignedPreKeySignature(), bundle.getIdentityKey()); - - try { - SessionBuilder builder = new SessionBuilder(axolotlStore, address); - builder.process(preKeyBundle); - XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey()); - sessions.put(address, session); - if (Config.X509_VERIFICATION) { - verifySessionWithPEP(session); - } else { - FingerprintStatus status = getFingerprintTrust(bundle.getIdentityKey().getFingerprint().replaceAll("\\s","")); - boolean verified = status != null && status.isVerified(); - fetchStatusMap.put(address, verified ? FetchStatus.SUCCESS_VERIFIED : FetchStatus.SUCCESS); - finishBuildingSessionsFromPEP(address); - } - } catch (UntrustedIdentityException | InvalidKeyException e) { - Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": " - + e.getClass().getName() + ", " + e.getMessage()); - fetchStatusMap.put(address, FetchStatus.ERROR); - finishBuildingSessionsFromPEP(address); - } - } else { - fetchStatusMap.put(address, FetchStatus.ERROR); - Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while building session:" + packet.findChild("error")); - finishBuildingSessionsFromPEP(address); - } - } - }); - } catch (InvalidJidException e) { - Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got address with invalid jid: " + address.getName()); - } - } - - public Set findDevicesWithoutSession(final Conversation conversation) { - Set addresses = new HashSet<>(); - for(Jid jid : getCryptoTargets(conversation)) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Finding devices without session for " + jid); - if (deviceIds.get(jid) != null) { - for (Integer foreignId : this.deviceIds.get(jid)) { - AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId); - if (sessions.get(address) == null) { - IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey(); - if (identityKey != null) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache..."); - XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey); - sessions.put(address, session); - } else { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + jid + ":" + foreignId); - if (fetchStatusMap.get(address) != FetchStatus.ERROR) { - addresses.add(address); - } else { - Log.d(Config.LOGTAG, getLogprefix(account) + "skipping over " + address + " because it's broken"); - } - } - } - } - } else { - Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Have no target devices in PEP!"); - } - } - if (deviceIds.get(account.getJid().toBareJid()) != null) { - for (Integer ownId : this.deviceIds.get(account.getJid().toBareJid())) { - AxolotlAddress address = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), ownId); - if (sessions.get(address) == null) { - IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey(); - if (identityKey != null) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache..."); - XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey); - sessions.put(address, session); - } else { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + account.getJid().toBareJid() + ":" + ownId); - if (fetchStatusMap.get(address) != FetchStatus.ERROR) { - addresses.add(address); - } else { - Log.d(Config.LOGTAG,getLogprefix(account)+"skipping over "+address+" because it's broken"); - } - } - } - } - } - - return addresses; - } - - public boolean createSessionsIfNeeded(final Conversation conversation) { - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Creating axolotl sessions if needed..."); - boolean newSessions = false; - Set addresses = findDevicesWithoutSession(conversation); - for (AxolotlAddress address : addresses) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Processing device: " + address.toString()); - FetchStatus status = fetchStatusMap.get(address); - if (status == null || status == FetchStatus.TIMEOUT) { - fetchStatusMap.put(address, FetchStatus.PENDING); - this.buildSessionFromPEP(address); - newSessions = true; - } else if (status == FetchStatus.PENDING) { - newSessions = true; - } else { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already fetching bundle for " + address.toString()); - } - } - - return newSessions; - } - - public boolean trustedSessionVerified(final Conversation conversation) { - Set sessions = findSessionsForConversation(conversation); - sessions.addAll(findOwnSessions()); - boolean verified = false; - for(XmppAxolotlSession session : sessions) { - if (session.getTrust().isTrustedAndActive()) { - if (session.getTrust().getTrust() == FingerprintStatus.Trust.VERIFIED_X509) { - verified = true; - } else { - return false; - } - } - } - return verified; - } - - public boolean hasPendingKeyFetches(Account account, List jids) { - AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), 0); - if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)) { - return true; - } - for(Jid jid : jids) { - AxolotlAddress foreignAddress = new AxolotlAddress(jid.toBareJid().toPreppedString(), 0); - if (fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING)) { - return true; - } - } - return false; - } - - @Nullable - private XmppAxolotlMessage buildHeader(Conversation conversation) { - final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage( - account.getJid().toBareJid(), getOwnDeviceId()); - - Set remoteSessions = findSessionsForConversation(conversation); - Set ownSessions = findOwnSessions(); - if (remoteSessions.isEmpty()) { - return null; - } - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl foreign keyElements..."); - for (XmppAxolotlSession session : remoteSessions) { - Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString()); - axolotlMessage.addDevice(session); - } - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl own keyElements..."); - for (XmppAxolotlSession session : ownSessions) { - Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString()); - axolotlMessage.addDevice(session); - } - - return axolotlMessage; - } - - @Nullable - public XmppAxolotlMessage encrypt(Message message) { - XmppAxolotlMessage axolotlMessage = buildHeader(message.getConversation()); - - if (axolotlMessage != null) { - final String content; - if (message.hasFileOnRemoteHost()) { - content = message.getFileParams().url.toString(); - } else { - content = message.getBody(); - } - try { - axolotlMessage.encrypt(content); - } catch (CryptoFailedException e) { - Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage()); - return null; - } - } - - return axolotlMessage; - } - - public void preparePayloadMessage(final Message message, final boolean delay) { - executor.execute(new Runnable() { - @Override - public void run() { - XmppAxolotlMessage axolotlMessage = encrypt(message); - if (axolotlMessage == null) { - mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED); - //mXmppConnectionService.updateConversationUi(); - } else { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Generated message, caching: " + message.getUuid()); - messageCache.put(message.getUuid(), axolotlMessage); - mXmppConnectionService.resendMessage(message, delay); - } - } - }); - } - - public void prepareKeyTransportMessage(final Conversation conversation, final OnMessageCreatedCallback onMessageCreatedCallback) { - executor.execute(new Runnable() { - @Override - public void run() { - XmppAxolotlMessage axolotlMessage = buildHeader(conversation); - onMessageCreatedCallback.run(axolotlMessage); - } - }); - } - - public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) { - XmppAxolotlMessage axolotlMessage = messageCache.get(message.getUuid()); - if (axolotlMessage != null) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache hit: " + message.getUuid()); - messageCache.remove(message.getUuid()); - } else { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache miss: " + message.getUuid()); - } - return axolotlMessage; - } - - private XmppAxolotlSession recreateUncachedSession(AxolotlAddress address) { - IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey(); - return (identityKey != null) - ? new XmppAxolotlSession(account, axolotlStore, address, identityKey) - : null; - } - - private XmppAxolotlSession getReceivingSession(XmppAxolotlMessage message) { - AxolotlAddress senderAddress = new AxolotlAddress(message.getFrom().toString(), - message.getSenderDeviceId()); - XmppAxolotlSession session = sessions.get(senderAddress); - if (session == null) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Account: " + account.getJid() + " No axolotl session found while parsing received message " + message); - session = recreateUncachedSession(senderAddress); - if (session == null) { - session = new XmppAxolotlSession(account, axolotlStore, senderAddress); - } - } - return session; - } - - public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message) { - XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null; - - XmppAxolotlSession session = getReceivingSession(message); - try { - plaintextMessage = message.decrypt(session, getOwnDeviceId()); - Integer preKeyId = session.getPreKeyId(); - if (preKeyId != null) { - publishBundlesIfNeeded(false, false); - session.resetPreKeyId(); - } - } catch (CryptoFailedException e) { - Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message: " + e.getMessage()); - } - - if (session.isFresh() && plaintextMessage != null) { - putFreshSession(session); - } - - return plaintextMessage; - } - - public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message) { - XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage; - - XmppAxolotlSession session = getReceivingSession(message); - keyTransportMessage = message.getParameters(session, getOwnDeviceId()); - - if (session.isFresh() && keyTransportMessage != null) { - putFreshSession(session); - } - - return keyTransportMessage; - } - - private void putFreshSession(XmppAxolotlSession session) { - Log.d(Config.LOGTAG,"put fresh session"); - sessions.put(session); - if (Config.X509_VERIFICATION) { - if (session.getIdentityKey() != null) { - verifySessionWithPEP(session); - } else { - Log.e(Config.LOGTAG,account.getJid().toBareJid()+": identity key was empty after reloading for x509 verification"); - } - } - } + public static final String PEP_PREFIX = "eu.siacs.conversations.axolotl"; + public static final String PEP_DEVICE_LIST = PEP_PREFIX + ".devicelist"; + public static final String PEP_DEVICE_LIST_NOTIFY = PEP_DEVICE_LIST + "+notify"; + public static final String PEP_BUNDLES = PEP_PREFIX + ".bundles"; + public static final String PEP_VERIFICATION = PEP_PREFIX + ".verification"; + + public static final String LOGPREFIX = "AxolotlService"; + + public static final int NUM_KEYS_TO_PUBLISH = 100; + public static final int publishTriesThreshold = 3; + + private final Account account; + private final XmppConnectionService mXmppConnectionService; + private final SQLiteAxolotlStore axolotlStore; + private final SessionMap sessions; + private final Map> deviceIds; + private final Map messageCache; + private final FetchStatusMap fetchStatusMap; + private final SerialSingleThreadExecutor executor; + private int numPublishTriesOnEmptyPep = 0; + private boolean pepBroken = false; + + @Override + public void onAdvancedStreamFeaturesAvailable(Account account) { + if (Config.supportOmemo() + && account.getXmppConnection() != null + && account.getXmppConnection().getFeatures().pep()) { + publishBundlesIfNeeded(true, false); + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": skipping OMEMO initialization"); + } + } + + public boolean fetchMapHasErrors(List jids) { + for (Jid jid : jids) { + if (deviceIds.get(jid) != null) { + for (Integer foreignId : this.deviceIds.get(jid)) { + AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId); + if (fetchStatusMap.getAll(address).containsValue(FetchStatus.ERROR)) { + return true; + } + } + } + } + return false; + } + + public void preVerifyFingerprint(Contact contact, String fingerprint) { + axolotlStore.preVerifyFingerprint(contact.getAccount(), contact.getJid().toBareJid().toPreppedString(), fingerprint); + } + + public void preVerifyFingerprint(Account account, String fingerprint) { + axolotlStore.preVerifyFingerprint(account, account.getJid().toBareJid().toPreppedString(), fingerprint); + } + + private static class AxolotlAddressMap { + protected Map> map; + protected final Object MAP_LOCK = new Object(); + + public AxolotlAddressMap() { + this.map = new HashMap<>(); + } + + public void put(AxolotlAddress address, T value) { + synchronized (MAP_LOCK) { + Map devices = map.get(address.getName()); + if (devices == null) { + devices = new HashMap<>(); + map.put(address.getName(), devices); + } + devices.put(address.getDeviceId(), value); + } + } + + public T get(AxolotlAddress address) { + synchronized (MAP_LOCK) { + Map devices = map.get(address.getName()); + if (devices == null) { + return null; + } + return devices.get(address.getDeviceId()); + } + } + + public Map getAll(AxolotlAddress address) { + synchronized (MAP_LOCK) { + Map devices = map.get(address.getName()); + if (devices == null) { + return new HashMap<>(); + } + return devices; + } + } + + public boolean hasAny(AxolotlAddress address) { + synchronized (MAP_LOCK) { + Map devices = map.get(address.getName()); + return devices != null && !devices.isEmpty(); + } + } + + public void clear() { + map.clear(); + } + + } + + private static class SessionMap extends AxolotlAddressMap { + private final XmppConnectionService xmppConnectionService; + private final Account account; + + public SessionMap(XmppConnectionService service, SQLiteAxolotlStore store, Account account) { + super(); + this.xmppConnectionService = service; + this.account = account; + this.fillMap(store); + } + + private void putDevicesForJid(String bareJid, List deviceIds, SQLiteAxolotlStore store) { + for (Integer deviceId : deviceIds) { + AxolotlAddress axolotlAddress = new AxolotlAddress(bareJid, deviceId); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building session for remote address: " + axolotlAddress.toString()); + IdentityKey identityKey = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey(); + if (Config.X509_VERIFICATION) { + X509Certificate certificate = store.getFingerprintCertificate(identityKey.getFingerprint().replaceAll("\\s", "")); + if (certificate != null) { + Bundle information = CryptoHelper.extractCertificateInformation(certificate); + try { + final String cn = information.getString("subject_cn"); + final Jid jid = Jid.fromString(bareJid); + Log.d(Config.LOGTAG, "setting common name for " + jid + " to " + cn); + account.getRoster().getContact(jid).setCommonName(cn); + } catch (final InvalidJidException ignored) { + //ignored + } + } + } + this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress, identityKey)); + } + } + + private void fillMap(SQLiteAxolotlStore store) { + List deviceIds = store.getSubDeviceSessions(account.getJid().toBareJid().toPreppedString()); + putDevicesForJid(account.getJid().toBareJid().toPreppedString(), deviceIds, store); + for (Contact contact : account.getRoster().getContacts()) { + Jid bareJid = contact.getJid().toBareJid(); + String address = bareJid.toString(); + deviceIds = store.getSubDeviceSessions(address); + putDevicesForJid(address, deviceIds, store); + } + + } + + @Override + public void put(AxolotlAddress address, XmppAxolotlSession value) { + super.put(address, value); + value.setNotFresh(); + xmppConnectionService.syncRosterToDisk(account); //TODO why? + } + + public void put(XmppAxolotlSession session) { + this.put(session.getRemoteAddress(), session); + } + } + + public enum FetchStatus { + PENDING, + SUCCESS, + SUCCESS_VERIFIED, + TIMEOUT, + ERROR + } + + private static class FetchStatusMap extends AxolotlAddressMap { + + public void clearErrorFor(Jid jid) { + synchronized (MAP_LOCK) { + Map devices = this.map.get(jid.toBareJid().toPreppedString()); + if (devices == null) { + return; + } + for (Map.Entry entry : devices.entrySet()) { + if (entry.getValue() == FetchStatus.ERROR) { + Log.d(Config.LOGTAG, "resetting error for " + jid.toBareJid() + "(" + entry.getKey() + ")"); + entry.setValue(FetchStatus.TIMEOUT); + } + } + } + } + } + + public static String getLogprefix(Account account) { + return LOGPREFIX + " (" + account.getJid().toBareJid().toString() + "): "; + } + + public AxolotlService(Account account, XmppConnectionService connectionService) { + if (Security.getProvider("BC") == null) { + Security.addProvider(new BouncyCastleProvider()); + } + this.mXmppConnectionService = connectionService; + this.account = account; + this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService); + this.deviceIds = new HashMap<>(); + this.messageCache = new HashMap<>(); + this.sessions = new SessionMap(mXmppConnectionService, axolotlStore, account); + this.fetchStatusMap = new FetchStatusMap(); + this.executor = new SerialSingleThreadExecutor(); + } + + public String getOwnFingerprint() { + return axolotlStore.getIdentityKeyPair().getPublicKey().getFingerprint().replaceAll("\\s", ""); + } + + public Set getKeysWithTrust(FingerprintStatus status) { + return axolotlStore.getContactKeysWithTrust(account.getJid().toBareJid().toPreppedString(), status); + } + + public Set getKeysWithTrust(FingerprintStatus status, Jid jid) { + return axolotlStore.getContactKeysWithTrust(jid.toBareJid().toPreppedString(), status); + } + + public Set getKeysWithTrust(FingerprintStatus status, List jids) { + Set keys = new HashSet<>(); + for (Jid jid : jids) { + keys.addAll(axolotlStore.getContactKeysWithTrust(jid.toPreppedString(), status)); + } + return keys; + } + + public long getNumTrustedKeys(Jid jid) { + return axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toPreppedString()); + } + + public boolean anyTargetHasNoTrustedKeys(List jids) { + for (Jid jid : jids) { + if (axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toPreppedString()) == 0) { + return true; + } + } + return false; + } + + private AxolotlAddress getAddressForJid(Jid jid) { + return new AxolotlAddress(jid.toPreppedString(), 0); + } + + public Set findOwnSessions() { + AxolotlAddress ownAddress = getAddressForJid(account.getJid().toBareJid()); + return new HashSet<>(this.sessions.getAll(ownAddress).values()); + } + + private Set findSessionsForContact(Contact contact) { + AxolotlAddress contactAddress = getAddressForJid(contact.getJid()); + return new HashSet<>(this.sessions.getAll(contactAddress).values()); + } + + private Set findSessionsForConversation(Conversation conversation) { + HashSet sessions = new HashSet<>(); + for (Jid jid : conversation.getAcceptedCryptoTargets()) { + sessions.addAll(this.sessions.getAll(getAddressForJid(jid)).values()); + } + return sessions; + } + + public Set getFingerprintsForOwnSessions() { + Set fingerprints = new HashSet<>(); + for (XmppAxolotlSession session : findOwnSessions()) { + fingerprints.add(session.getFingerprint()); + } + return fingerprints; + } + + public Set getFingerprintsForContact(final Contact contact) { + Set fingerprints = new HashSet<>(); + for (XmppAxolotlSession session : findSessionsForContact(contact)) { + fingerprints.add(session.getFingerprint()); + } + return fingerprints; + } + + private boolean hasAny(Jid jid) { + return sessions.hasAny(getAddressForJid(jid)); + } + + public boolean isPepBroken() { + return this.pepBroken; + } + + public void resetBrokenness() { + this.pepBroken = false; + numPublishTriesOnEmptyPep = 0; + } + + public void clearErrorsInFetchStatusMap(Jid jid) { + fetchStatusMap.clearErrorFor(jid); + } + + public void regenerateKeys(boolean wipeOther) { + axolotlStore.regenerate(); + sessions.clear(); + fetchStatusMap.clear(); + publishBundlesIfNeeded(true, wipeOther); + } + + public int getOwnDeviceId() { + return axolotlStore.getLocalRegistrationId(); + } + + public Set getOwnDeviceIds() { + return this.deviceIds.get(account.getJid().toBareJid()); + } + + public void registerDevices(final Jid jid, @NonNull final Set deviceIds) { + if (jid.toBareJid().equals(account.getJid().toBareJid())) { + if (!deviceIds.isEmpty()) { + Log.d(Config.LOGTAG, getLogprefix(account) + "Received non-empty own device list. Resetting publish attempts and pepBroken status."); + pepBroken = false; + numPublishTriesOnEmptyPep = 0; + } + if (deviceIds.contains(getOwnDeviceId())) { + deviceIds.remove(getOwnDeviceId()); + } else { + publishOwnDeviceId(deviceIds); + } + for (Integer deviceId : deviceIds) { + AxolotlAddress ownDeviceAddress = new AxolotlAddress(jid.toBareJid().toPreppedString(), deviceId); + if (sessions.get(ownDeviceAddress) == null) { + buildSessionFromPEP(ownDeviceAddress); + } + } + } + Set expiredDevices = new HashSet<>(axolotlStore.getSubDeviceSessions(jid.toBareJid().toPreppedString())); + expiredDevices.removeAll(deviceIds); + for (Integer deviceId : expiredDevices) { + AxolotlAddress address = new AxolotlAddress(jid.toBareJid().toPreppedString(), deviceId); + XmppAxolotlSession session = sessions.get(address); + if (session != null && session.getFingerprint() != null) { + if (session.getTrust().isActive()) { + session.setTrust(session.getTrust().toInactive()); + } + } + } + Set newDevices = new HashSet<>(deviceIds); + for (Integer deviceId : newDevices) { + AxolotlAddress address = new AxolotlAddress(jid.toBareJid().toPreppedString(), deviceId); + XmppAxolotlSession session = sessions.get(address); + if (session != null && session.getFingerprint() != null) { + if (!session.getTrust().isActive()) { + session.setTrust(session.getTrust().toActive()); + } + } + } + this.deviceIds.put(jid, deviceIds); + mXmppConnectionService.keyStatusUpdated(null); + } + + public void wipeOtherPepDevices() { + if (pepBroken) { + Log.d(Config.LOGTAG, getLogprefix(account) + "wipeOtherPepDevices called, but PEP is broken. Ignoring... "); + return; + } + Set deviceIds = new HashSet<>(); + deviceIds.add(getOwnDeviceId()); + IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Wiping all other devices from Pep:" + publish); + mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + // TODO: implement this! + } + }); + } + + public void purgeKey(final String fingerprint) { + axolotlStore.setFingerprintStatus(fingerprint.replaceAll("\\s", ""), FingerprintStatus.createCompromised()); + } + + public void publishOwnDeviceIdIfNeeded() { + if (pepBroken) { + Log.d(Config.LOGTAG, getLogprefix(account) + "publishOwnDeviceIdIfNeeded called, but PEP is broken. Ignoring... "); + return; + } + IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().toBareJid()); + mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.TIMEOUT) { + Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids."); + } else { + Element item = mXmppConnectionService.getIqParser().getItem(packet); + Set deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); + if (!deviceIds.contains(getOwnDeviceId())) { + publishOwnDeviceId(deviceIds); + } + } + } + }); + } + + public void publishOwnDeviceId(Set deviceIds) { + Set deviceIdsCopy = new HashSet<>(deviceIds); + if (!deviceIdsCopy.contains(getOwnDeviceId())) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Own device " + getOwnDeviceId() + " not in PEP devicelist."); + if (deviceIdsCopy.isEmpty()) { + if (numPublishTriesOnEmptyPep >= publishTriesThreshold) { + Log.w(Config.LOGTAG, getLogprefix(account) + "Own device publish attempt threshold exceeded, aborting..."); + pepBroken = true; + return; + } else { + numPublishTriesOnEmptyPep++; + Log.w(Config.LOGTAG, getLogprefix(account) + "Own device list empty, attempting to publish (try " + numPublishTriesOnEmptyPep + ")"); + } + } else { + numPublishTriesOnEmptyPep = 0; + } + deviceIdsCopy.add(getOwnDeviceId()); + IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIdsCopy); + mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.ERROR) { + pepBroken = true; + Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + packet.findChild("error")); + } + } + }); + } + } + + public void publishDeviceVerificationAndBundle(final SignedPreKeyRecord signedPreKeyRecord, + final Set preKeyRecords, + final boolean announceAfter, + final boolean wipe) { + try { + IdentityKey axolotlPublicKey = axolotlStore.getIdentityKeyPair().getPublicKey(); + PrivateKey x509PrivateKey = KeyChain.getPrivateKey(mXmppConnectionService, account.getPrivateKeyAlias()); + X509Certificate[] chain = KeyChain.getCertificateChain(mXmppConnectionService, account.getPrivateKeyAlias()); + Signature verifier = Signature.getInstance("sha256WithRSA"); + verifier.initSign(x509PrivateKey, mXmppConnectionService.getRNG()); + verifier.update(axolotlPublicKey.serialize()); + byte[] signature = verifier.sign(); + IqPacket packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId()); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": publish verification for device " + getOwnDeviceId()); + mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe); + } + }); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void publishBundlesIfNeeded(final boolean announce, final boolean wipe) { + if (pepBroken) { + Log.d(Config.LOGTAG, getLogprefix(account) + "publishBundlesIfNeeded called, but PEP is broken. Ignoring... "); + return; + } + IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().toBareJid(), getOwnDeviceId()); + mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + + if (packet.getType() == IqPacket.TYPE.TIMEOUT) { + return; //ignore timeout. do nothing + } + + if (packet.getType() == IqPacket.TYPE.ERROR) { + Element error = packet.findChild("error"); + if (error == null || !error.hasChild("item-not-found")) { + pepBroken = true; + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "request for device bundles came back with something other than item-not-found" + packet); + return; + } + } + + PreKeyBundle bundle = mXmppConnectionService.getIqParser().bundle(packet); + Map keys = mXmppConnectionService.getIqParser().preKeyPublics(packet); + boolean flush = false; + if (bundle == null) { + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid bundle:" + packet); + bundle = new PreKeyBundle(-1, -1, -1, null, -1, null, null, null); + flush = true; + } + if (keys == null) { + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid prekeys:" + packet); + } + try { + boolean changed = false; + // Validate IdentityKey + IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair(); + if (flush || !identityKeyPair.getPublicKey().equals(bundle.getIdentityKey())) { + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP."); + changed = true; + } + + // Validate signedPreKeyRecord + ID + SignedPreKeyRecord signedPreKeyRecord; + int numSignedPreKeys = axolotlStore.loadSignedPreKeys().size(); + try { + signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId()); + if (flush + || !bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey()) + || !Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) { + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP."); + signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1); + axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord); + changed = true; + } + } catch (InvalidKeyIdException e) { + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP."); + signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1); + axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord); + changed = true; + } + + // Validate PreKeys + Set preKeyRecords = new HashSet<>(); + if (keys != null) { + for (Integer id : keys.keySet()) { + try { + PreKeyRecord preKeyRecord = axolotlStore.loadPreKey(id); + if (preKeyRecord.getKeyPair().getPublicKey().equals(keys.get(id))) { + preKeyRecords.add(preKeyRecord); + } + } catch (InvalidKeyIdException ignored) { + } + } + } + int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords.size(); + if (newKeys > 0) { + List newRecords = KeyHelper.generatePreKeys( + axolotlStore.getCurrentPreKeyId() + 1, newKeys); + preKeyRecords.addAll(newRecords); + for (PreKeyRecord record : newRecords) { + axolotlStore.storePreKey(record.getId(), record); + } + changed = true; + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding " + newKeys + " new preKeys to PEP."); + } + + + if (changed) { + if (account.getPrivateKeyAlias() != null && Config.X509_VERIFICATION) { + mXmppConnectionService.publishDisplayName(account); + publishDeviceVerificationAndBundle(signedPreKeyRecord, preKeyRecords, announce, wipe); + } else { + publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announce, wipe); + } + } else { + Log.d(Config.LOGTAG, getLogprefix(account) + "Bundle " + getOwnDeviceId() + " in PEP was current"); + if (wipe) { + wipeOtherPepDevices(); + } else if (announce) { + Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId()); + publishOwnDeviceIdIfNeeded(); + } + } + } catch (InvalidKeyException e) { + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage()); + } + } + }); + } + + private void publishDeviceBundle(SignedPreKeyRecord signedPreKeyRecord, + Set preKeyRecords, + final boolean announceAfter, + final boolean wipe) { + IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles( + signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(), + preKeyRecords, getOwnDeviceId()); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing: " + publish); + mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.RESULT) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Successfully published bundle. "); + if (wipe) { + wipeOtherPepDevices(); + } else if (announceAfter) { + Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId()); + publishOwnDeviceIdIfNeeded(); + } + } else if (packet.getType() == IqPacket.TYPE.ERROR) { + pepBroken = true; + Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " + packet.findChild("error")); + } + } + }); + } + + public enum AxolotlCapability { + FULL, + MISSING_PRESENCE, + MISSING_KEYS, + WRONG_CONFIGURATION, + NO_MEMBERS + } + + public boolean isConversationAxolotlCapable(Conversation conversation) { + return isConversationAxolotlCapableDetailed(conversation).first == AxolotlCapability.FULL; + } + + public Pair isConversationAxolotlCapableDetailed(Conversation conversation) { + if (conversation.getMode() == Conversation.MODE_SINGLE || (conversation.getMucOptions().membersOnly() && conversation.getMucOptions().nonanonymous())) { + final List jids = getCryptoTargets(conversation); + for (Jid jid : jids) { + if (!hasAny(jid) && (!deviceIds.containsKey(jid) || deviceIds.get(jid).isEmpty())) { + if (conversation.getAccount().getRoster().getContact(jid).mutualPresenceSubscription()) { + return new Pair<>(AxolotlCapability.MISSING_KEYS, jid); + } else { + return new Pair<>(AxolotlCapability.MISSING_PRESENCE, jid); + } + } + } + if (jids.size() > 0) { + return new Pair<>(AxolotlCapability.FULL, null); + } else { + return new Pair<>(AxolotlCapability.NO_MEMBERS, null); + } + } else { + return new Pair<>(AxolotlCapability.WRONG_CONFIGURATION, null); + } + } + + public List getCryptoTargets(Conversation conversation) { + final List jids; + if (conversation.getMode() == Conversation.MODE_SINGLE) { + jids = Arrays.asList(conversation.getJid().toBareJid()); + } else { + jids = conversation.getMucOptions().getMembers(); + } + return jids; + } + + public FingerprintStatus getFingerprintTrust(String fingerprint) { + return axolotlStore.getFingerprintStatus(fingerprint); + } + + public X509Certificate getFingerprintCertificate(String fingerprint) { + return axolotlStore.getFingerprintCertificate(fingerprint); + } + + public void setFingerprintTrust(String fingerprint, FingerprintStatus status) { + axolotlStore.setFingerprintStatus(fingerprint, status); + } + + private void verifySessionWithPEP(final XmppAxolotlSession session) { + Log.d(Config.LOGTAG, "trying to verify fresh session (" + session.getRemoteAddress().getName() + ") with pep"); + final AxolotlAddress address = session.getRemoteAddress(); + final IdentityKey identityKey = session.getIdentityKey(); + try { + IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(Jid.fromString(address.getName()), address.getDeviceId()); + mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + Pair verification = mXmppConnectionService.getIqParser().verification(packet); + if (verification != null) { + try { + Signature verifier = Signature.getInstance("sha256WithRSA"); + verifier.initVerify(verification.first[0]); + verifier.update(identityKey.serialize()); + if (verifier.verify(verification.second)) { + try { + mXmppConnectionService.getMemorizingTrustManager().getNonInteractive().checkClientTrusted(verification.first, "RSA"); + String fingerprint = session.getFingerprint(); + Log.d(Config.LOGTAG, "verified session with x.509 signature. fingerprint was: " + fingerprint); + setFingerprintTrust(fingerprint, FingerprintStatus.createActiveVerified(true)); + axolotlStore.setFingerprintCertificate(fingerprint, verification.first[0]); + fetchStatusMap.put(address, FetchStatus.SUCCESS_VERIFIED); + Bundle information = CryptoHelper.extractCertificateInformation(verification.first[0]); + try { + final String cn = information.getString("subject_cn"); + final Jid jid = Jid.fromString(address.getName()); + Log.d(Config.LOGTAG, "setting common name for " + jid + " to " + cn); + account.getRoster().getContact(jid).setCommonName(cn); + } catch (final InvalidJidException ignored) { + //ignored + } + finishBuildingSessionsFromPEP(address); + return; + } catch (Exception e) { + Log.d(Config.LOGTAG, "could not verify certificate"); + } + } + } catch (Exception e) { + Log.d(Config.LOGTAG, "error during verification " + e.getMessage()); + } + } else { + Log.d(Config.LOGTAG, "no verification found"); + } + fetchStatusMap.put(address, FetchStatus.SUCCESS); + finishBuildingSessionsFromPEP(address); + } + }); + } catch (InvalidJidException e) { + fetchStatusMap.put(address, FetchStatus.SUCCESS); + finishBuildingSessionsFromPEP(address); + } + } + + private void finishBuildingSessionsFromPEP(final AxolotlAddress address) { + AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), 0); + Map own = fetchStatusMap.getAll(ownAddress); + Map remote = fetchStatusMap.getAll(address); + if (!own.containsValue(FetchStatus.PENDING) && !remote.containsValue(FetchStatus.PENDING)) { + FetchStatus report = null; + if (own.containsValue(FetchStatus.SUCCESS) || remote.containsValue(FetchStatus.SUCCESS)) { + report = FetchStatus.SUCCESS; + } else if (own.containsValue(FetchStatus.SUCCESS_VERIFIED) || remote.containsValue(FetchStatus.SUCCESS_VERIFIED)) { + report = FetchStatus.SUCCESS_VERIFIED; + } else if (own.containsValue(FetchStatus.ERROR) || remote.containsValue(FetchStatus.ERROR)) { + report = FetchStatus.ERROR; + } + mXmppConnectionService.keyStatusUpdated(report); + } + } + + private void buildSessionFromPEP(final AxolotlAddress address) { + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new sesstion for " + address.toString()); + if (address.getDeviceId() == getOwnDeviceId()) { + throw new AssertionError("We should NEVER build a session with ourselves. What happened here?!"); + } + + try { + IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice( + Jid.fromString(address.getName()), address.getDeviceId()); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Retrieving bundle: " + bundlesPacket); + mXmppConnectionService.sendIqPacket(account, bundlesPacket, new OnIqPacketReceived() { + + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.TIMEOUT) { + fetchStatusMap.put(address, FetchStatus.TIMEOUT); + } else if (packet.getType() == IqPacket.TYPE.RESULT) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing..."); + final IqParser parser = mXmppConnectionService.getIqParser(); + final List preKeyBundleList = parser.preKeys(packet); + final PreKeyBundle bundle = parser.bundle(packet); + if (preKeyBundleList.isEmpty() || bundle == null) { + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet); + fetchStatusMap.put(address, FetchStatus.ERROR); + finishBuildingSessionsFromPEP(address); + return; + } + Random random = new Random(); + final PreKeyBundle preKey = preKeyBundleList.get(random.nextInt(preKeyBundleList.size())); + if (preKey == null) { + //should never happen + fetchStatusMap.put(address, FetchStatus.ERROR); + finishBuildingSessionsFromPEP(address); + return; + } + + final PreKeyBundle preKeyBundle = new PreKeyBundle(0, address.getDeviceId(), + preKey.getPreKeyId(), preKey.getPreKey(), + bundle.getSignedPreKeyId(), bundle.getSignedPreKey(), + bundle.getSignedPreKeySignature(), bundle.getIdentityKey()); + + try { + SessionBuilder builder = new SessionBuilder(axolotlStore, address); + builder.process(preKeyBundle); + XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey()); + sessions.put(address, session); + if (Config.X509_VERIFICATION) { + verifySessionWithPEP(session); + } else { + FingerprintStatus status = getFingerprintTrust(bundle.getIdentityKey().getFingerprint().replaceAll("\\s", "")); + boolean verified = status != null && status.isVerified(); + fetchStatusMap.put(address, verified ? FetchStatus.SUCCESS_VERIFIED : FetchStatus.SUCCESS); + finishBuildingSessionsFromPEP(address); + } + } catch (UntrustedIdentityException | InvalidKeyException e) { + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": " + + e.getClass().getName() + ", " + e.getMessage()); + fetchStatusMap.put(address, FetchStatus.ERROR); + finishBuildingSessionsFromPEP(address); + } + } else { + fetchStatusMap.put(address, FetchStatus.ERROR); + Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while building session:" + packet.findChild("error")); + finishBuildingSessionsFromPEP(address); + } + } + }); + } catch (InvalidJidException e) { + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got address with invalid jid: " + address.getName()); + } + } + + public Set findDevicesWithoutSession(final Conversation conversation) { + Set addresses = new HashSet<>(); + for (Jid jid : getCryptoTargets(conversation)) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Finding devices without session for " + jid); + if (deviceIds.get(jid) != null) { + for (Integer foreignId : this.deviceIds.get(jid)) { + AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId); + if (sessions.get(address) == null) { + IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey(); + if (identityKey != null) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache..."); + XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey); + sessions.put(address, session); + } else { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + jid + ":" + foreignId); + if (fetchStatusMap.get(address) != FetchStatus.ERROR) { + addresses.add(address); + } else { + Log.d(Config.LOGTAG, getLogprefix(account) + "skipping over " + address + " because it's broken"); + } + } + } + } + } else { + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Have no target devices in PEP!"); + } + } + if (deviceIds.get(account.getJid().toBareJid()) != null) { + for (Integer ownId : this.deviceIds.get(account.getJid().toBareJid())) { + AxolotlAddress address = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), ownId); + if (sessions.get(address) == null) { + IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey(); + if (identityKey != null) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache..."); + XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey); + sessions.put(address, session); + } else { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + account.getJid().toBareJid() + ":" + ownId); + if (fetchStatusMap.get(address) != FetchStatus.ERROR) { + addresses.add(address); + } else { + Log.d(Config.LOGTAG, getLogprefix(account) + "skipping over " + address + " because it's broken"); + } + } + } + } + } + + return addresses; + } + + public boolean createSessionsIfNeeded(final Conversation conversation) { + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Creating axolotl sessions if needed..."); + boolean newSessions = false; + Set addresses = findDevicesWithoutSession(conversation); + for (AxolotlAddress address : addresses) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Processing device: " + address.toString()); + FetchStatus status = fetchStatusMap.get(address); + if (status == null || status == FetchStatus.TIMEOUT) { + fetchStatusMap.put(address, FetchStatus.PENDING); + this.buildSessionFromPEP(address); + newSessions = true; + } else if (status == FetchStatus.PENDING) { + newSessions = true; + } else { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already fetching bundle for " + address.toString()); + } + } + + return newSessions; + } + + public boolean trustedSessionVerified(final Conversation conversation) { + Set sessions = findSessionsForConversation(conversation); + sessions.addAll(findOwnSessions()); + boolean verified = false; + for (XmppAxolotlSession session : sessions) { + if (session.getTrust().isTrustedAndActive()) { + if (session.getTrust().getTrust() == FingerprintStatus.Trust.VERIFIED_X509) { + verified = true; + } else { + return false; + } + } + } + return verified; + } + + public boolean hasPendingKeyFetches(Account account, List jids) { + AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), 0); + if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)) { + return true; + } + for (Jid jid : jids) { + AxolotlAddress foreignAddress = new AxolotlAddress(jid.toBareJid().toPreppedString(), 0); + if (fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING)) { + return true; + } + } + return false; + } + + @Nullable + private XmppAxolotlMessage buildHeader(Conversation conversation) { + final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage( + account.getJid().toBareJid(), getOwnDeviceId()); + + Set remoteSessions = findSessionsForConversation(conversation); + Set ownSessions = findOwnSessions(); + if (remoteSessions.isEmpty()) { + return null; + } + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl foreign keyElements..."); + for (XmppAxolotlSession session : remoteSessions) { + Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString()); + axolotlMessage.addDevice(session); + } + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl own keyElements..."); + for (XmppAxolotlSession session : ownSessions) { + Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString()); + axolotlMessage.addDevice(session); + } + + return axolotlMessage; + } + + @Nullable + public XmppAxolotlMessage encrypt(Message message) { + XmppAxolotlMessage axolotlMessage = buildHeader(message.getConversation()); + + if (axolotlMessage != null) { + final String content; + if (message.hasFileOnRemoteHost()) { + content = message.getFileParams().url.toString(); + } else { + content = message.getBody(); + } + try { + axolotlMessage.encrypt(content); + } catch (CryptoFailedException e) { + Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage()); + return null; + } + } + + return axolotlMessage; + } + + public void preparePayloadMessage(final Message message, final boolean delay) { + executor.execute(new Runnable() { + @Override + public void run() { + XmppAxolotlMessage axolotlMessage = encrypt(message); + if (axolotlMessage == null) { + mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED); + //mXmppConnectionService.updateConversationUi(); + } else { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Generated message, caching: " + message.getUuid()); + messageCache.put(message.getUuid(), axolotlMessage); + mXmppConnectionService.resendMessage(message, delay); + } + } + }); + } + + public void prepareKeyTransportMessage(final Conversation conversation, final OnMessageCreatedCallback onMessageCreatedCallback) { + executor.execute(new Runnable() { + @Override + public void run() { + XmppAxolotlMessage axolotlMessage = buildHeader(conversation); + onMessageCreatedCallback.run(axolotlMessage); + } + }); + } + + public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) { + XmppAxolotlMessage axolotlMessage = messageCache.get(message.getUuid()); + if (axolotlMessage != null) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache hit: " + message.getUuid()); + messageCache.remove(message.getUuid()); + } else { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache miss: " + message.getUuid()); + } + return axolotlMessage; + } + + private XmppAxolotlSession recreateUncachedSession(AxolotlAddress address) { + IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey(); + return (identityKey != null) + ? new XmppAxolotlSession(account, axolotlStore, address, identityKey) + : null; + } + + private XmppAxolotlSession getReceivingSession(XmppAxolotlMessage message) { + AxolotlAddress senderAddress = new AxolotlAddress(message.getFrom().toString(), + message.getSenderDeviceId()); + XmppAxolotlSession session = sessions.get(senderAddress); + if (session == null) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Account: " + account.getJid() + " No axolotl session found while parsing received message " + message); + session = recreateUncachedSession(senderAddress); + if (session == null) { + session = new XmppAxolotlSession(account, axolotlStore, senderAddress); + } + } + return session; + } + + public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message) { + XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null; + + XmppAxolotlSession session = getReceivingSession(message); + try { + plaintextMessage = message.decrypt(session, getOwnDeviceId()); + Integer preKeyId = session.getPreKeyId(); + if (preKeyId != null) { + publishBundlesIfNeeded(false, false); + session.resetPreKeyId(); + } + } catch (CryptoFailedException e) { + Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message: " + e.getMessage()); + } + + if (session.isFresh() && plaintextMessage != null) { + putFreshSession(session); + } + + return plaintextMessage; + } + + public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message) { + XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage; + + XmppAxolotlSession session = getReceivingSession(message); + keyTransportMessage = message.getParameters(session, getOwnDeviceId()); + + if (session.isFresh() && keyTransportMessage != null) { + putFreshSession(session); + } + + return keyTransportMessage; + } + + private void putFreshSession(XmppAxolotlSession session) { + Log.d(Config.LOGTAG, "put fresh session"); + sessions.put(session); + if (Config.X509_VERIFICATION) { + if (session.getIdentityKey() != null) { + verifySessionWithPEP(session); + } else { + Log.e(Config.LOGTAG, account.getJid().toBareJid() + ": identity key was empty after reloading for x509 verification"); + } + } + } } diff --git a/src/main/java/de/pixart/messenger/crypto/axolotl/CryptoFailedException.java b/src/main/java/de/pixart/messenger/crypto/axolotl/CryptoFailedException.java index b29d7cb30..da847393b 100644 --- a/src/main/java/de/pixart/messenger/crypto/axolotl/CryptoFailedException.java +++ b/src/main/java/de/pixart/messenger/crypto/axolotl/CryptoFailedException.java @@ -1,7 +1,7 @@ package de.pixart.messenger.crypto.axolotl; public class CryptoFailedException extends Exception { - public CryptoFailedException(Exception e){ - super(e); - } + public CryptoFailedException(Exception e) { + super(e); + } } diff --git a/src/main/java/de/pixart/messenger/crypto/axolotl/FingerprintStatus.java b/src/main/java/de/pixart/messenger/crypto/axolotl/FingerprintStatus.java index db67617b9..3be469b5a 100644 --- a/src/main/java/de/pixart/messenger/crypto/axolotl/FingerprintStatus.java +++ b/src/main/java/de/pixart/messenger/crypto/axolotl/FingerprintStatus.java @@ -32,8 +32,8 @@ public class FingerprintStatus { public ContentValues toContentValues() { final ContentValues contentValues = new ContentValues(); - contentValues.put(SQLiteAxolotlStore.TRUST,trust.toString()); - contentValues.put(SQLiteAxolotlStore.ACTIVE,active ? 1 : 0); + contentValues.put(SQLiteAxolotlStore.TRUST, trust.toString()); + contentValues.put(SQLiteAxolotlStore.ACTIVE, active ? 1 : 0); return contentValues; } @@ -41,7 +41,7 @@ public class FingerprintStatus { final FingerprintStatus status = new FingerprintStatus(); try { status.trust = Trust.valueOf(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.TRUST))); - } catch(IllegalArgumentException e) { + } catch (IllegalArgumentException e) { status.trust = Trust.UNTRUSTED; } status.active = cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.ACTIVE)) > 0; diff --git a/src/main/java/de/pixart/messenger/crypto/axolotl/NoSessionsCreatedException.java b/src/main/java/de/pixart/messenger/crypto/axolotl/NoSessionsCreatedException.java index 5d0a7547a..92635a364 100644 --- a/src/main/java/de/pixart/messenger/crypto/axolotl/NoSessionsCreatedException.java +++ b/src/main/java/de/pixart/messenger/crypto/axolotl/NoSessionsCreatedException.java @@ -1,4 +1,4 @@ package de.pixart.messenger.crypto.axolotl; -public class NoSessionsCreatedException extends Throwable{ +public class NoSessionsCreatedException extends Throwable { } diff --git a/src/main/java/de/pixart/messenger/crypto/axolotl/OnMessageCreatedCallback.java b/src/main/java/de/pixart/messenger/crypto/axolotl/OnMessageCreatedCallback.java index a6daeb196..088831661 100644 --- a/src/main/java/de/pixart/messenger/crypto/axolotl/OnMessageCreatedCallback.java +++ b/src/main/java/de/pixart/messenger/crypto/axolotl/OnMessageCreatedCallback.java @@ -1,5 +1,5 @@ package de.pixart.messenger.crypto.axolotl; public interface OnMessageCreatedCallback { - void run(XmppAxolotlMessage message); + void run(XmppAxolotlMessage message); } diff --git a/src/main/java/de/pixart/messenger/crypto/axolotl/SQLiteAxolotlStore.java b/src/main/java/de/pixart/messenger/crypto/axolotl/SQLiteAxolotlStore.java index 2980bf7cc..c45e5045e 100644 --- a/src/main/java/de/pixart/messenger/crypto/axolotl/SQLiteAxolotlStore.java +++ b/src/main/java/de/pixart/messenger/crypto/axolotl/SQLiteAxolotlStore.java @@ -25,421 +25,421 @@ import de.pixart.messenger.services.XmppConnectionService; public class SQLiteAxolotlStore implements AxolotlStore { - public static final String PREKEY_TABLENAME = "prekeys"; - public static final String SIGNED_PREKEY_TABLENAME = "signed_prekeys"; - public static final String SESSION_TABLENAME = "sessions"; - public static final String IDENTITIES_TABLENAME = "identities"; - public static final String ACCOUNT = "account"; - public static final String DEVICE_ID = "device_id"; - public static final String ID = "id"; - public static final String KEY = "key"; - public static final String FINGERPRINT = "fingerprint"; - public static final String NAME = "name"; - public static final String TRUSTED = "trusted"; //no longer used - public static final String TRUST = "trust"; - public static final String ACTIVE = "active"; - public static final String OWN = "ownkey"; - public static final String CERTIFICATE = "certificate"; - - public static final String JSONKEY_REGISTRATION_ID = "axolotl_reg_id"; - public static final String JSONKEY_CURRENT_PREKEY_ID = "axolotl_cur_prekey_id"; - - private static final int NUM_TRUSTS_TO_CACHE = 100; - - private final Account account; - private final XmppConnectionService mXmppConnectionService; - - private IdentityKeyPair identityKeyPair; - private int localRegistrationId; - private int currentPreKeyId = 0; - - private final LruCache trustCache = - new LruCache(NUM_TRUSTS_TO_CACHE) { - @Override - protected FingerprintStatus create(String fingerprint) { - return mXmppConnectionService.databaseBackend.getFingerprintStatus(account, fingerprint); - } - }; - - private static IdentityKeyPair generateIdentityKeyPair() { - Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Generating axolotl IdentityKeyPair..."); - ECKeyPair identityKeyPairKeys = Curve.generateKeyPair(); - return new IdentityKeyPair(new IdentityKey(identityKeyPairKeys.getPublicKey()), - identityKeyPairKeys.getPrivateKey()); - } - - private static int generateRegistrationId() { - Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Generating axolotl registration ID..."); - return KeyHelper.generateRegistrationId(true); - } - - public SQLiteAxolotlStore(Account account, XmppConnectionService service) { - this.account = account; - this.mXmppConnectionService = service; - this.localRegistrationId = loadRegistrationId(); - this.currentPreKeyId = loadCurrentPreKeyId(); - for (SignedPreKeyRecord record : loadSignedPreKeys()) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got Axolotl signed prekey record:" + record.getId()); - } - } - - public int getCurrentPreKeyId() { - return currentPreKeyId; - } - - // -------------------------------------- - // IdentityKeyStore - // -------------------------------------- - - private IdentityKeyPair loadIdentityKeyPair() { - synchronized (mXmppConnectionService) { - IdentityKeyPair ownKey = mXmppConnectionService.databaseBackend.loadOwnIdentityKeyPair(account); - - if (ownKey != null) { - return ownKey; - } else { - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve own IdentityKeyPair"); - ownKey = generateIdentityKeyPair(); - mXmppConnectionService.databaseBackend.storeOwnIdentityKeyPair(account, ownKey); - } - return ownKey; - } - } - - private int loadRegistrationId() { - return loadRegistrationId(false); - } - - private int loadRegistrationId(boolean regenerate) { - String regIdString = this.account.getKey(JSONKEY_REGISTRATION_ID); - int reg_id; - if (!regenerate && regIdString != null) { - reg_id = Integer.valueOf(regIdString); - } else { - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve axolotl registration id for account " + account.getJid()); - reg_id = generateRegistrationId(); - boolean success = this.account.setKey(JSONKEY_REGISTRATION_ID, Integer.toString(reg_id)); - if (success) { - mXmppConnectionService.databaseBackend.updateAccount(account); - } else { - Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to write new key to the database!"); - } - } - return reg_id; - } - - private int loadCurrentPreKeyId() { - String prekeyIdString = this.account.getKey(JSONKEY_CURRENT_PREKEY_ID); - int prekey_id; - if (prekeyIdString != null) { - prekey_id = Integer.valueOf(prekeyIdString); - } else { - Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve current prekey id for account " + account.getJid()); - prekey_id = 0; - } - return prekey_id; - } - - public void regenerate() { - mXmppConnectionService.databaseBackend.wipeAxolotlDb(account); - trustCache.evictAll(); - account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(0)); - identityKeyPair = loadIdentityKeyPair(); - localRegistrationId = loadRegistrationId(true); - currentPreKeyId = 0; - mXmppConnectionService.updateAccountUi(); - } - - /** - * Get the local client's identity key pair. - * - * @return The local client's persistent identity key pair. - */ - @Override - public IdentityKeyPair getIdentityKeyPair() { - if (identityKeyPair == null) { - identityKeyPair = loadIdentityKeyPair(); - } - return identityKeyPair; - } - - /** - * Return the local client's registration ID. - *

- * Clients should maintain a registration ID, a random number - * between 1 and 16380 that's generated once at install time. - * - * @return the local client's registration ID. - */ - @Override - public int getLocalRegistrationId() { - return localRegistrationId; - } - - /** - * Save a remote client's identity key - *

- * Store a remote client's identity key as trusted. - * - * @param name The name of the remote client. - * @param identityKey The remote client's identity key. - */ - @Override - public void saveIdentity(String name, IdentityKey identityKey) { - if (!mXmppConnectionService.databaseBackend.loadIdentityKeys(account, name).contains(identityKey)) { - String fingerprint = identityKey.getFingerprint().replaceAll("\\s", ""); - FingerprintStatus status = getFingerprintStatus(fingerprint); - if (status == null) { - status = FingerprintStatus.createActiveUndecided(); //default for new keys - } else { - status = status.toActive(); - } - mXmppConnectionService.databaseBackend.storeIdentityKey(account, name, identityKey, status); - trustCache.remove(fingerprint); - } - } - - /** - * Verify a remote client's identity key. - *

- * Determine whether a remote client's identity is trusted. Convention is - * that the TextSecure protocol is 'trust on first use.' This means that - * an identity key is considered 'trusted' if there is no entry for the recipient - * in the local store, or if it matches the saved key for a recipient in the local - * store. Only if it mismatches an entry in the local store is it considered - * 'untrusted.' - * - * @param name The name of the remote client. - * @param identityKey The identity key to verify. - * @return true if trusted, false if untrusted. - */ - @Override - public boolean isTrustedIdentity(String name, IdentityKey identityKey) { - return true; - } - - public FingerprintStatus getFingerprintStatus(String fingerprint) { - return (fingerprint == null)? null : trustCache.get(fingerprint); - } - - public void setFingerprintStatus(String fingerprint, FingerprintStatus status) { - mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, fingerprint, status); - trustCache.remove(fingerprint); - } - - public void setFingerprintCertificate(String fingerprint, X509Certificate x509Certificate) { - mXmppConnectionService.databaseBackend.setIdentityKeyCertificate(account, fingerprint, x509Certificate); - } - - public X509Certificate getFingerprintCertificate(String fingerprint) { - return mXmppConnectionService.databaseBackend.getIdentityKeyCertifcate(account, fingerprint); - } - - public Set getContactKeysWithTrust(String bareJid, FingerprintStatus status) { - return mXmppConnectionService.databaseBackend.loadIdentityKeys(account, bareJid, status); - } - - public long getContactNumTrustedKeys(String bareJid) { - return mXmppConnectionService.databaseBackend.numTrustedKeys(account, bareJid); - } - - // -------------------------------------- - // SessionStore - // -------------------------------------- - - /** - * Returns a copy of the {@link SessionRecord} corresponding to the recipientId + deviceId tuple, - * or a new SessionRecord if one does not currently exist. - *

- * It is important that implementations return a copy of the current durable information. The - * returned SessionRecord may be modified, but those changes should not have an effect on the - * durable session state (what is returned by subsequent calls to this method) without the - * store method being called here first. - * - * @param address The name and device ID of the remote client. - * @return a copy of the SessionRecord corresponding to the recipientId + deviceId tuple, or - * a new SessionRecord if one does not currently exist. - */ - @Override - public SessionRecord loadSession(AxolotlAddress address) { - SessionRecord session = mXmppConnectionService.databaseBackend.loadSession(this.account, address); - return (session != null) ? session : new SessionRecord(); - } - - /** - * Returns all known devices with active sessions for a recipient - * - * @param name the name of the client. - * @return all known sub-devices with active sessions. - */ - @Override - public List getSubDeviceSessions(String name) { - return mXmppConnectionService.databaseBackend.getSubDeviceSessions(account, - new AxolotlAddress(name, 0)); - } - - /** - * Commit to storage the {@link SessionRecord} for a given recipientId + deviceId tuple. - * - * @param address the address of the remote client. - * @param record the current SessionRecord for the remote client. - */ - @Override - public void storeSession(AxolotlAddress address, SessionRecord record) { - mXmppConnectionService.databaseBackend.storeSession(account, address, record); - } - - /** - * Determine whether there is a committed {@link SessionRecord} for a recipientId + deviceId tuple. - * - * @param address the address of the remote client. - * @return true if a {@link SessionRecord} exists, false otherwise. - */ - @Override - public boolean containsSession(AxolotlAddress address) { - return mXmppConnectionService.databaseBackend.containsSession(account, address); - } - - /** - * Remove a {@link SessionRecord} for a recipientId + deviceId tuple. - * - * @param address the address of the remote client. - */ - @Override - public void deleteSession(AxolotlAddress address) { - mXmppConnectionService.databaseBackend.deleteSession(account, address); - } - - /** - * Remove the {@link SessionRecord}s corresponding to all devices of a recipientId. - * - * @param name the name of the remote client. - */ - @Override - public void deleteAllSessions(String name) { - AxolotlAddress address = new AxolotlAddress(name, 0); - mXmppConnectionService.databaseBackend.deleteAllSessions(account, - address); - } - - // -------------------------------------- - // PreKeyStore - // -------------------------------------- - - /** - * Load a local PreKeyRecord. - * - * @param preKeyId the ID of the local PreKeyRecord. - * @return the corresponding PreKeyRecord. - * @throws InvalidKeyIdException when there is no corresponding PreKeyRecord. - */ - @Override - public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException { - PreKeyRecord record = mXmppConnectionService.databaseBackend.loadPreKey(account, preKeyId); - if (record == null) { - throw new InvalidKeyIdException("No such PreKeyRecord: " + preKeyId); - } - return record; - } - - /** - * Store a local PreKeyRecord. - * - * @param preKeyId the ID of the PreKeyRecord to store. - * @param record the PreKeyRecord. - */ - @Override - public void storePreKey(int preKeyId, PreKeyRecord record) { - mXmppConnectionService.databaseBackend.storePreKey(account, record); - currentPreKeyId = preKeyId; - boolean success = this.account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(preKeyId)); - if (success) { - mXmppConnectionService.databaseBackend.updateAccount(account); - } else { - Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to write new prekey id to the database!"); - } - } - - /** - * @param preKeyId A PreKeyRecord ID. - * @return true if the store has a record for the preKeyId, otherwise false. - */ - @Override - public boolean containsPreKey(int preKeyId) { - return mXmppConnectionService.databaseBackend.containsPreKey(account, preKeyId); - } - - /** - * Delete a PreKeyRecord from local storage. - * - * @param preKeyId The ID of the PreKeyRecord to remove. - */ - @Override - public void removePreKey(int preKeyId) { - mXmppConnectionService.databaseBackend.deletePreKey(account, preKeyId); - } - - // -------------------------------------- - // SignedPreKeyStore - // -------------------------------------- - - /** - * Load a local SignedPreKeyRecord. - * - * @param signedPreKeyId the ID of the local SignedPreKeyRecord. - * @return the corresponding SignedPreKeyRecord. - * @throws InvalidKeyIdException when there is no corresponding SignedPreKeyRecord. - */ - @Override - public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException { - SignedPreKeyRecord record = mXmppConnectionService.databaseBackend.loadSignedPreKey(account, signedPreKeyId); - if (record == null) { - throw new InvalidKeyIdException("No such SignedPreKeyRecord: " + signedPreKeyId); - } - return record; - } - - /** - * Load all local SignedPreKeyRecords. - * - * @return All stored SignedPreKeyRecords. - */ - @Override - public List loadSignedPreKeys() { - return mXmppConnectionService.databaseBackend.loadSignedPreKeys(account); - } - - /** - * Store a local SignedPreKeyRecord. - * - * @param signedPreKeyId the ID of the SignedPreKeyRecord to store. - * @param record the SignedPreKeyRecord. - */ - @Override - public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) { - mXmppConnectionService.databaseBackend.storeSignedPreKey(account, record); - } - - /** - * @param signedPreKeyId A SignedPreKeyRecord ID. - * @return true if the store has a record for the signedPreKeyId, otherwise false. - */ - @Override - public boolean containsSignedPreKey(int signedPreKeyId) { - return mXmppConnectionService.databaseBackend.containsSignedPreKey(account, signedPreKeyId); - } - - /** - * Delete a SignedPreKeyRecord from local storage. - * - * @param signedPreKeyId The ID of the SignedPreKeyRecord to remove. - */ - @Override - public void removeSignedPreKey(int signedPreKeyId) { - mXmppConnectionService.databaseBackend.deleteSignedPreKey(account, signedPreKeyId); - } - - public void preVerifyFingerprint(Account account, String name, String fingerprint) { - mXmppConnectionService.databaseBackend.storePreVerification(account,name,fingerprint,FingerprintStatus.createInactiveVerified()); - } + public static final String PREKEY_TABLENAME = "prekeys"; + public static final String SIGNED_PREKEY_TABLENAME = "signed_prekeys"; + public static final String SESSION_TABLENAME = "sessions"; + public static final String IDENTITIES_TABLENAME = "identities"; + public static final String ACCOUNT = "account"; + public static final String DEVICE_ID = "device_id"; + public static final String ID = "id"; + public static final String KEY = "key"; + public static final String FINGERPRINT = "fingerprint"; + public static final String NAME = "name"; + public static final String TRUSTED = "trusted"; //no longer used + public static final String TRUST = "trust"; + public static final String ACTIVE = "active"; + public static final String OWN = "ownkey"; + public static final String CERTIFICATE = "certificate"; + + public static final String JSONKEY_REGISTRATION_ID = "axolotl_reg_id"; + public static final String JSONKEY_CURRENT_PREKEY_ID = "axolotl_cur_prekey_id"; + + private static final int NUM_TRUSTS_TO_CACHE = 100; + + private final Account account; + private final XmppConnectionService mXmppConnectionService; + + private IdentityKeyPair identityKeyPair; + private int localRegistrationId; + private int currentPreKeyId = 0; + + private final LruCache trustCache = + new LruCache(NUM_TRUSTS_TO_CACHE) { + @Override + protected FingerprintStatus create(String fingerprint) { + return mXmppConnectionService.databaseBackend.getFingerprintStatus(account, fingerprint); + } + }; + + private static IdentityKeyPair generateIdentityKeyPair() { + Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Generating axolotl IdentityKeyPair..."); + ECKeyPair identityKeyPairKeys = Curve.generateKeyPair(); + return new IdentityKeyPair(new IdentityKey(identityKeyPairKeys.getPublicKey()), + identityKeyPairKeys.getPrivateKey()); + } + + private static int generateRegistrationId() { + Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Generating axolotl registration ID..."); + return KeyHelper.generateRegistrationId(true); + } + + public SQLiteAxolotlStore(Account account, XmppConnectionService service) { + this.account = account; + this.mXmppConnectionService = service; + this.localRegistrationId = loadRegistrationId(); + this.currentPreKeyId = loadCurrentPreKeyId(); + for (SignedPreKeyRecord record : loadSignedPreKeys()) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got Axolotl signed prekey record:" + record.getId()); + } + } + + public int getCurrentPreKeyId() { + return currentPreKeyId; + } + + // -------------------------------------- + // IdentityKeyStore + // -------------------------------------- + + private IdentityKeyPair loadIdentityKeyPair() { + synchronized (mXmppConnectionService) { + IdentityKeyPair ownKey = mXmppConnectionService.databaseBackend.loadOwnIdentityKeyPair(account); + + if (ownKey != null) { + return ownKey; + } else { + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve own IdentityKeyPair"); + ownKey = generateIdentityKeyPair(); + mXmppConnectionService.databaseBackend.storeOwnIdentityKeyPair(account, ownKey); + } + return ownKey; + } + } + + private int loadRegistrationId() { + return loadRegistrationId(false); + } + + private int loadRegistrationId(boolean regenerate) { + String regIdString = this.account.getKey(JSONKEY_REGISTRATION_ID); + int reg_id; + if (!regenerate && regIdString != null) { + reg_id = Integer.valueOf(regIdString); + } else { + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve axolotl registration id for account " + account.getJid()); + reg_id = generateRegistrationId(); + boolean success = this.account.setKey(JSONKEY_REGISTRATION_ID, Integer.toString(reg_id)); + if (success) { + mXmppConnectionService.databaseBackend.updateAccount(account); + } else { + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to write new key to the database!"); + } + } + return reg_id; + } + + private int loadCurrentPreKeyId() { + String prekeyIdString = this.account.getKey(JSONKEY_CURRENT_PREKEY_ID); + int prekey_id; + if (prekeyIdString != null) { + prekey_id = Integer.valueOf(prekeyIdString); + } else { + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve current prekey id for account " + account.getJid()); + prekey_id = 0; + } + return prekey_id; + } + + public void regenerate() { + mXmppConnectionService.databaseBackend.wipeAxolotlDb(account); + trustCache.evictAll(); + account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(0)); + identityKeyPair = loadIdentityKeyPair(); + localRegistrationId = loadRegistrationId(true); + currentPreKeyId = 0; + mXmppConnectionService.updateAccountUi(); + } + + /** + * Get the local client's identity key pair. + * + * @return The local client's persistent identity key pair. + */ + @Override + public IdentityKeyPair getIdentityKeyPair() { + if (identityKeyPair == null) { + identityKeyPair = loadIdentityKeyPair(); + } + return identityKeyPair; + } + + /** + * Return the local client's registration ID. + *

+ * Clients should maintain a registration ID, a random number + * between 1 and 16380 that's generated once at install time. + * + * @return the local client's registration ID. + */ + @Override + public int getLocalRegistrationId() { + return localRegistrationId; + } + + /** + * Save a remote client's identity key + *

+ * Store a remote client's identity key as trusted. + * + * @param name The name of the remote client. + * @param identityKey The remote client's identity key. + */ + @Override + public void saveIdentity(String name, IdentityKey identityKey) { + if (!mXmppConnectionService.databaseBackend.loadIdentityKeys(account, name).contains(identityKey)) { + String fingerprint = identityKey.getFingerprint().replaceAll("\\s", ""); + FingerprintStatus status = getFingerprintStatus(fingerprint); + if (status == null) { + status = FingerprintStatus.createActiveUndecided(); //default for new keys + } else { + status = status.toActive(); + } + mXmppConnectionService.databaseBackend.storeIdentityKey(account, name, identityKey, status); + trustCache.remove(fingerprint); + } + } + + /** + * Verify a remote client's identity key. + *

+ * Determine whether a remote client's identity is trusted. Convention is + * that the TextSecure protocol is 'trust on first use.' This means that + * an identity key is considered 'trusted' if there is no entry for the recipient + * in the local store, or if it matches the saved key for a recipient in the local + * store. Only if it mismatches an entry in the local store is it considered + * 'untrusted.' + * + * @param name The name of the remote client. + * @param identityKey The identity key to verify. + * @return true if trusted, false if untrusted. + */ + @Override + public boolean isTrustedIdentity(String name, IdentityKey identityKey) { + return true; + } + + public FingerprintStatus getFingerprintStatus(String fingerprint) { + return (fingerprint == null) ? null : trustCache.get(fingerprint); + } + + public void setFingerprintStatus(String fingerprint, FingerprintStatus status) { + mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, fingerprint, status); + trustCache.remove(fingerprint); + } + + public void setFingerprintCertificate(String fingerprint, X509Certificate x509Certificate) { + mXmppConnectionService.databaseBackend.setIdentityKeyCertificate(account, fingerprint, x509Certificate); + } + + public X509Certificate getFingerprintCertificate(String fingerprint) { + return mXmppConnectionService.databaseBackend.getIdentityKeyCertifcate(account, fingerprint); + } + + public Set getContactKeysWithTrust(String bareJid, FingerprintStatus status) { + return mXmppConnectionService.databaseBackend.loadIdentityKeys(account, bareJid, status); + } + + public long getContactNumTrustedKeys(String bareJid) { + return mXmppConnectionService.databaseBackend.numTrustedKeys(account, bareJid); + } + + // -------------------------------------- + // SessionStore + // -------------------------------------- + + /** + * Returns a copy of the {@link SessionRecord} corresponding to the recipientId + deviceId tuple, + * or a new SessionRecord if one does not currently exist. + *

+ * It is important that implementations return a copy of the current durable information. The + * returned SessionRecord may be modified, but those changes should not have an effect on the + * durable session state (what is returned by subsequent calls to this method) without the + * store method being called here first. + * + * @param address The name and device ID of the remote client. + * @return a copy of the SessionRecord corresponding to the recipientId + deviceId tuple, or + * a new SessionRecord if one does not currently exist. + */ + @Override + public SessionRecord loadSession(AxolotlAddress address) { + SessionRecord session = mXmppConnectionService.databaseBackend.loadSession(this.account, address); + return (session != null) ? session : new SessionRecord(); + } + + /** + * Returns all known devices with active sessions for a recipient + * + * @param name the name of the client. + * @return all known sub-devices with active sessions. + */ + @Override + public List getSubDeviceSessions(String name) { + return mXmppConnectionService.databaseBackend.getSubDeviceSessions(account, + new AxolotlAddress(name, 0)); + } + + /** + * Commit to storage the {@link SessionRecord} for a given recipientId + deviceId tuple. + * + * @param address the address of the remote client. + * @param record the current SessionRecord for the remote client. + */ + @Override + public void storeSession(AxolotlAddress address, SessionRecord record) { + mXmppConnectionService.databaseBackend.storeSession(account, address, record); + } + + /** + * Determine whether there is a committed {@link SessionRecord} for a recipientId + deviceId tuple. + * + * @param address the address of the remote client. + * @return true if a {@link SessionRecord} exists, false otherwise. + */ + @Override + public boolean containsSession(AxolotlAddress address) { + return mXmppConnectionService.databaseBackend.containsSession(account, address); + } + + /** + * Remove a {@link SessionRecord} for a recipientId + deviceId tuple. + * + * @param address the address of the remote client. + */ + @Override + public void deleteSession(AxolotlAddress address) { + mXmppConnectionService.databaseBackend.deleteSession(account, address); + } + + /** + * Remove the {@link SessionRecord}s corresponding to all devices of a recipientId. + * + * @param name the name of the remote client. + */ + @Override + public void deleteAllSessions(String name) { + AxolotlAddress address = new AxolotlAddress(name, 0); + mXmppConnectionService.databaseBackend.deleteAllSessions(account, + address); + } + + // -------------------------------------- + // PreKeyStore + // -------------------------------------- + + /** + * Load a local PreKeyRecord. + * + * @param preKeyId the ID of the local PreKeyRecord. + * @return the corresponding PreKeyRecord. + * @throws InvalidKeyIdException when there is no corresponding PreKeyRecord. + */ + @Override + public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException { + PreKeyRecord record = mXmppConnectionService.databaseBackend.loadPreKey(account, preKeyId); + if (record == null) { + throw new InvalidKeyIdException("No such PreKeyRecord: " + preKeyId); + } + return record; + } + + /** + * Store a local PreKeyRecord. + * + * @param preKeyId the ID of the PreKeyRecord to store. + * @param record the PreKeyRecord. + */ + @Override + public void storePreKey(int preKeyId, PreKeyRecord record) { + mXmppConnectionService.databaseBackend.storePreKey(account, record); + currentPreKeyId = preKeyId; + boolean success = this.account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(preKeyId)); + if (success) { + mXmppConnectionService.databaseBackend.updateAccount(account); + } else { + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to write new prekey id to the database!"); + } + } + + /** + * @param preKeyId A PreKeyRecord ID. + * @return true if the store has a record for the preKeyId, otherwise false. + */ + @Override + public boolean containsPreKey(int preKeyId) { + return mXmppConnectionService.databaseBackend.containsPreKey(account, preKeyId); + } + + /** + * Delete a PreKeyRecord from local storage. + * + * @param preKeyId The ID of the PreKeyRecord to remove. + */ + @Override + public void removePreKey(int preKeyId) { + mXmppConnectionService.databaseBackend.deletePreKey(account, preKeyId); + } + + // -------------------------------------- + // SignedPreKeyStore + // -------------------------------------- + + /** + * Load a local SignedPreKeyRecord. + * + * @param signedPreKeyId the ID of the local SignedPreKeyRecord. + * @return the corresponding SignedPreKeyRecord. + * @throws InvalidKeyIdException when there is no corresponding SignedPreKeyRecord. + */ + @Override + public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException { + SignedPreKeyRecord record = mXmppConnectionService.databaseBackend.loadSignedPreKey(account, signedPreKeyId); + if (record == null) { + throw new InvalidKeyIdException("No such SignedPreKeyRecord: " + signedPreKeyId); + } + return record; + } + + /** + * Load all local SignedPreKeyRecords. + * + * @return All stored SignedPreKeyRecords. + */ + @Override + public List loadSignedPreKeys() { + return mXmppConnectionService.databaseBackend.loadSignedPreKeys(account); + } + + /** + * Store a local SignedPreKeyRecord. + * + * @param signedPreKeyId the ID of the SignedPreKeyRecord to store. + * @param record the SignedPreKeyRecord. + */ + @Override + public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) { + mXmppConnectionService.databaseBackend.storeSignedPreKey(account, record); + } + + /** + * @param signedPreKeyId A SignedPreKeyRecord ID. + * @return true if the store has a record for the signedPreKeyId, otherwise false. + */ + @Override + public boolean containsSignedPreKey(int signedPreKeyId) { + return mXmppConnectionService.databaseBackend.containsSignedPreKey(account, signedPreKeyId); + } + + /** + * Delete a SignedPreKeyRecord from local storage. + * + * @param signedPreKeyId The ID of the SignedPreKeyRecord to remove. + */ + @Override + public void removeSignedPreKey(int signedPreKeyId) { + mXmppConnectionService.databaseBackend.deleteSignedPreKey(account, signedPreKeyId); + } + + public void preVerifyFingerprint(Account account, String name, String fingerprint) { + mXmppConnectionService.databaseBackend.storePreVerification(account, name, fingerprint, FingerprintStatus.createInactiveVerified()); + } } diff --git a/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java index 431f81d9b..f49eefb5a 100644 --- a/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java @@ -26,227 +26,227 @@ import de.pixart.messenger.xml.Element; import de.pixart.messenger.xmpp.jid.Jid; public class XmppAxolotlMessage { - public static final String CONTAINERTAG = "encrypted"; - public static final String HEADER = "header"; - public static final String SOURCEID = "sid"; - public static final String KEYTAG = "key"; - public static final String REMOTEID = "rid"; - public static final String IVTAG = "iv"; - public static final String PAYLOAD = "payload"; - - private static final String KEYTYPE = "AES"; - private static final String CIPHERMODE = "AES/GCM/NoPadding"; - private static final String PROVIDER = "BC"; - - private byte[] innerKey; - private byte[] ciphertext = null; - private byte[] iv = null; - private final Map keys; - private final Jid from; - private final int sourceDeviceId; - - public static class XmppAxolotlPlaintextMessage { - private final String plaintext; - private final String fingerprint; - - public XmppAxolotlPlaintextMessage(String plaintext, String fingerprint) { - this.plaintext = plaintext; - this.fingerprint = fingerprint; - } - - public String getPlaintext() { - return plaintext; - } - - - public String getFingerprint() { - return fingerprint; - } - } - - public static class XmppAxolotlKeyTransportMessage { - private final String fingerprint; - private final byte[] key; - private final byte[] iv; - - public XmppAxolotlKeyTransportMessage(String fingerprint, byte[] key, byte[] iv) { - this.fingerprint = fingerprint; - this.key = key; - this.iv = iv; - } - - public String getFingerprint() { - return fingerprint; - } - - public byte[] getKey() { - return key; - } - - public byte[] getIv() { - return iv; - } - } - - private XmppAxolotlMessage(final Element axolotlMessage, final Jid from) throws IllegalArgumentException { - this.from = from; - Element header = axolotlMessage.findChild(HEADER); - try { - this.sourceDeviceId = Integer.parseInt(header.getAttribute(SOURCEID)); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("invalid source id"); - } - List keyElements = header.getChildren(); - this.keys = new HashMap<>(keyElements.size()); - for (Element keyElement : keyElements) { - switch (keyElement.getName()) { - case KEYTAG: - try { - Integer recipientId = Integer.parseInt(keyElement.getAttribute(REMOTEID)); - byte[] key = Base64.decode(keyElement.getContent().trim(), Base64.DEFAULT); - this.keys.put(recipientId, key); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("invalid remote id"); - } - break; - case IVTAG: - if (this.iv != null) { - throw new IllegalArgumentException("Duplicate iv entry"); - } - iv = Base64.decode(keyElement.getContent().trim(), Base64.DEFAULT); - break; - default: - Log.w(Config.LOGTAG, "Unexpected element in header: " + keyElement.toString()); - break; - } - } - Element payloadElement = axolotlMessage.findChild(PAYLOAD); - if (payloadElement != null) { - ciphertext = Base64.decode(payloadElement.getContent().trim(), Base64.DEFAULT); - } - } - - public XmppAxolotlMessage(Jid from, int sourceDeviceId) { - this.from = from; - this.sourceDeviceId = sourceDeviceId; - this.keys = new HashMap<>(); - this.iv = generateIv(); - this.innerKey = generateKey(); - } - - public static XmppAxolotlMessage fromElement(Element element, Jid from) { - return new XmppAxolotlMessage(element, from); - } - - private static byte[] generateKey() { - try { - KeyGenerator generator = KeyGenerator.getInstance(KEYTYPE); - generator.init(128); - return generator.generateKey().getEncoded(); - } catch (NoSuchAlgorithmException e) { - Log.e(Config.LOGTAG, e.getMessage()); - return null; - } - } - - private static byte[] generateIv() { - SecureRandom random = new SecureRandom(); - byte[] iv = new byte[16]; - random.nextBytes(iv); - return iv; - } - - public void encrypt(String plaintext) throws CryptoFailedException { - try { - SecretKey secretKey = new SecretKeySpec(innerKey, KEYTYPE); - IvParameterSpec ivSpec = new IvParameterSpec(iv); - Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER); - cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec); - this.ciphertext = cipher.doFinal(plaintext.getBytes()); - } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException - | IllegalBlockSizeException | BadPaddingException | NoSuchProviderException - | InvalidAlgorithmParameterException e) { - throw new CryptoFailedException(e); - } - } - - public Jid getFrom() { - return this.from; - } - - public int getSenderDeviceId() { - return sourceDeviceId; - } - - public byte[] getCiphertext() { - return ciphertext; - } - - public void addDevice(XmppAxolotlSession session) { - byte[] key = session.processSending(innerKey); - if (key != null) { - keys.put(session.getRemoteAddress().getDeviceId(), key); - } - } - - public byte[] getInnerKey() { - return innerKey; - } - - public byte[] getIV() { - return this.iv; - } - - public Element toElement() { - Element encryptionElement = new Element(CONTAINERTAG, AxolotlService.PEP_PREFIX); - Element headerElement = encryptionElement.addChild(HEADER); - headerElement.setAttribute(SOURCEID, sourceDeviceId); - for (Map.Entry keyEntry : keys.entrySet()) { - Element keyElement = new Element(KEYTAG); - keyElement.setAttribute(REMOTEID, keyEntry.getKey()); - keyElement.setContent(Base64.encodeToString(keyEntry.getValue(), Base64.NO_WRAP)); - headerElement.addChild(keyElement); - } - headerElement.addChild(IVTAG).setContent(Base64.encodeToString(iv, Base64.NO_WRAP)); - if (ciphertext != null) { - Element payload = encryptionElement.addChild(PAYLOAD); - payload.setContent(Base64.encodeToString(ciphertext, Base64.NO_WRAP)); - } - return encryptionElement; - } - - private byte[] unpackKey(XmppAxolotlSession session, Integer sourceDeviceId) { - byte[] encryptedKey = keys.get(sourceDeviceId); - return (encryptedKey != null) ? session.processReceiving(encryptedKey) : null; - } - - public XmppAxolotlKeyTransportMessage getParameters(XmppAxolotlSession session, Integer sourceDeviceId) { - byte[] key = unpackKey(session, sourceDeviceId); - return (key != null) - ? new XmppAxolotlKeyTransportMessage(session.getFingerprint(), key, getIV()) - : null; - } - - public XmppAxolotlPlaintextMessage decrypt(XmppAxolotlSession session, Integer sourceDeviceId) throws CryptoFailedException { - XmppAxolotlPlaintextMessage plaintextMessage = null; - byte[] key = unpackKey(session, sourceDeviceId); - if (key != null) { - try { - Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER); - SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE); - IvParameterSpec ivSpec = new IvParameterSpec(iv); - - cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); - - String plaintext = new String(cipher.doFinal(ciphertext)); - plaintextMessage = new XmppAxolotlPlaintextMessage(plaintext, session.getFingerprint()); - - } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException - | InvalidAlgorithmParameterException | IllegalBlockSizeException - | BadPaddingException | NoSuchProviderException e) { - throw new CryptoFailedException(e); - } - } - return plaintextMessage; - } + public static final String CONTAINERTAG = "encrypted"; + public static final String HEADER = "header"; + public static final String SOURCEID = "sid"; + public static final String KEYTAG = "key"; + public static final String REMOTEID = "rid"; + public static final String IVTAG = "iv"; + public static final String PAYLOAD = "payload"; + + private static final String KEYTYPE = "AES"; + private static final String CIPHERMODE = "AES/GCM/NoPadding"; + private static final String PROVIDER = "BC"; + + private byte[] innerKey; + private byte[] ciphertext = null; + private byte[] iv = null; + private final Map keys; + private final Jid from; + private final int sourceDeviceId; + + public static class XmppAxolotlPlaintextMessage { + private final String plaintext; + private final String fingerprint; + + public XmppAxolotlPlaintextMessage(String plaintext, String fingerprint) { + this.plaintext = plaintext; + this.fingerprint = fingerprint; + } + + public String getPlaintext() { + return plaintext; + } + + + public String getFingerprint() { + return fingerprint; + } + } + + public static class XmppAxolotlKeyTransportMessage { + private final String fingerprint; + private final byte[] key; + private final byte[] iv; + + public XmppAxolotlKeyTransportMessage(String fingerprint, byte[] key, byte[] iv) { + this.fingerprint = fingerprint; + this.key = key; + this.iv = iv; + } + + public String getFingerprint() { + return fingerprint; + } + + public byte[] getKey() { + return key; + } + + public byte[] getIv() { + return iv; + } + } + + private XmppAxolotlMessage(final Element axolotlMessage, final Jid from) throws IllegalArgumentException { + this.from = from; + Element header = axolotlMessage.findChild(HEADER); + try { + this.sourceDeviceId = Integer.parseInt(header.getAttribute(SOURCEID)); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("invalid source id"); + } + List keyElements = header.getChildren(); + this.keys = new HashMap<>(keyElements.size()); + for (Element keyElement : keyElements) { + switch (keyElement.getName()) { + case KEYTAG: + try { + Integer recipientId = Integer.parseInt(keyElement.getAttribute(REMOTEID)); + byte[] key = Base64.decode(keyElement.getContent().trim(), Base64.DEFAULT); + this.keys.put(recipientId, key); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("invalid remote id"); + } + break; + case IVTAG: + if (this.iv != null) { + throw new IllegalArgumentException("Duplicate iv entry"); + } + iv = Base64.decode(keyElement.getContent().trim(), Base64.DEFAULT); + break; + default: + Log.w(Config.LOGTAG, "Unexpected element in header: " + keyElement.toString()); + break; + } + } + Element payloadElement = axolotlMessage.findChild(PAYLOAD); + if (payloadElement != null) { + ciphertext = Base64.decode(payloadElement.getContent().trim(), Base64.DEFAULT); + } + } + + public XmppAxolotlMessage(Jid from, int sourceDeviceId) { + this.from = from; + this.sourceDeviceId = sourceDeviceId; + this.keys = new HashMap<>(); + this.iv = generateIv(); + this.innerKey = generateKey(); + } + + public static XmppAxolotlMessage fromElement(Element element, Jid from) { + return new XmppAxolotlMessage(element, from); + } + + private static byte[] generateKey() { + try { + KeyGenerator generator = KeyGenerator.getInstance(KEYTYPE); + generator.init(128); + return generator.generateKey().getEncoded(); + } catch (NoSuchAlgorithmException e) { + Log.e(Config.LOGTAG, e.getMessage()); + return null; + } + } + + private static byte[] generateIv() { + SecureRandom random = new SecureRandom(); + byte[] iv = new byte[16]; + random.nextBytes(iv); + return iv; + } + + public void encrypt(String plaintext) throws CryptoFailedException { + try { + SecretKey secretKey = new SecretKeySpec(innerKey, KEYTYPE); + IvParameterSpec ivSpec = new IvParameterSpec(iv); + Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec); + this.ciphertext = cipher.doFinal(plaintext.getBytes()); + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException + | IllegalBlockSizeException | BadPaddingException | NoSuchProviderException + | InvalidAlgorithmParameterException e) { + throw new CryptoFailedException(e); + } + } + + public Jid getFrom() { + return this.from; + } + + public int getSenderDeviceId() { + return sourceDeviceId; + } + + public byte[] getCiphertext() { + return ciphertext; + } + + public void addDevice(XmppAxolotlSession session) { + byte[] key = session.processSending(innerKey); + if (key != null) { + keys.put(session.getRemoteAddress().getDeviceId(), key); + } + } + + public byte[] getInnerKey() { + return innerKey; + } + + public byte[] getIV() { + return this.iv; + } + + public Element toElement() { + Element encryptionElement = new Element(CONTAINERTAG, AxolotlService.PEP_PREFIX); + Element headerElement = encryptionElement.addChild(HEADER); + headerElement.setAttribute(SOURCEID, sourceDeviceId); + for (Map.Entry keyEntry : keys.entrySet()) { + Element keyElement = new Element(KEYTAG); + keyElement.setAttribute(REMOTEID, keyEntry.getKey()); + keyElement.setContent(Base64.encodeToString(keyEntry.getValue(), Base64.NO_WRAP)); + headerElement.addChild(keyElement); + } + headerElement.addChild(IVTAG).setContent(Base64.encodeToString(iv, Base64.NO_WRAP)); + if (ciphertext != null) { + Element payload = encryptionElement.addChild(PAYLOAD); + payload.setContent(Base64.encodeToString(ciphertext, Base64.NO_WRAP)); + } + return encryptionElement; + } + + private byte[] unpackKey(XmppAxolotlSession session, Integer sourceDeviceId) { + byte[] encryptedKey = keys.get(sourceDeviceId); + return (encryptedKey != null) ? session.processReceiving(encryptedKey) : null; + } + + public XmppAxolotlKeyTransportMessage getParameters(XmppAxolotlSession session, Integer sourceDeviceId) { + byte[] key = unpackKey(session, sourceDeviceId); + return (key != null) + ? new XmppAxolotlKeyTransportMessage(session.getFingerprint(), key, getIV()) + : null; + } + + public XmppAxolotlPlaintextMessage decrypt(XmppAxolotlSession session, Integer sourceDeviceId) throws CryptoFailedException { + XmppAxolotlPlaintextMessage plaintextMessage = null; + byte[] key = unpackKey(session, sourceDeviceId); + if (key != null) { + try { + Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER); + SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE); + IvParameterSpec ivSpec = new IvParameterSpec(iv); + + cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); + + String plaintext = new String(cipher.doFinal(ciphertext)); + plaintextMessage = new XmppAxolotlPlaintextMessage(plaintext, session.getFingerprint()); + + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException + | InvalidAlgorithmParameterException | IllegalBlockSizeException + | BadPaddingException | NoSuchProviderException e) { + throw new CryptoFailedException(e); + } + } + return plaintextMessage; + } } diff --git a/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlSession.java b/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlSession.java index 566afef9a..27647fb66 100644 --- a/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlSession.java +++ b/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlSession.java @@ -23,113 +23,113 @@ import de.pixart.messenger.Config; import de.pixart.messenger.entities.Account; public class XmppAxolotlSession { - private final SessionCipher cipher; - private final SQLiteAxolotlStore sqLiteAxolotlStore; - private final AxolotlAddress remoteAddress; - private final Account account; - private IdentityKey identityKey; - private Integer preKeyId = null; - private boolean fresh = true; - - public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress, IdentityKey identityKey) { - this(account, store, remoteAddress); - this.identityKey = identityKey; - } - - public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress) { - this.cipher = new SessionCipher(store, remoteAddress); - this.remoteAddress = remoteAddress; - this.sqLiteAxolotlStore = store; - this.account = account; - } - - public Integer getPreKeyId() { - return preKeyId; - } - - public void resetPreKeyId() { - - preKeyId = null; - } - - public String getFingerprint() { - return identityKey == null ? null : identityKey.getFingerprint().replaceAll("\\s", ""); - } - - public IdentityKey getIdentityKey() { - return identityKey; - } - - public AxolotlAddress getRemoteAddress() { - return remoteAddress; - } - - public boolean isFresh() { - return fresh; - } - - public void setNotFresh() { - this.fresh = false; - } - - protected void setTrust(FingerprintStatus status) { - sqLiteAxolotlStore.setFingerprintStatus(getFingerprint(), status); - } - - public FingerprintStatus getTrust() { - FingerprintStatus status = sqLiteAxolotlStore.getFingerprintStatus(getFingerprint()); - return (status == null) ? FingerprintStatus.createActiveUndecided() : status; - } - - @Nullable - public byte[] processReceiving(byte[] encryptedKey) { - byte[] plaintext = null; - FingerprintStatus status = getTrust(); - if (!status.isCompromised()) { - try { - try { - PreKeyWhisperMessage message = new PreKeyWhisperMessage(encryptedKey); - if (!message.getPreKeyId().isPresent()) { - Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage did not contain a PreKeyId"); - return null; - } - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId()); - IdentityKey msgIdentityKey = message.getIdentityKey(); - if (this.identityKey != null && !this.identityKey.equals(msgIdentityKey)) { - Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Had session with fingerprint " + this.getFingerprint() + ", received message with fingerprint " + msgIdentityKey.getFingerprint()); - } else { - this.identityKey = msgIdentityKey; - plaintext = cipher.decrypt(message); - preKeyId = message.getPreKeyId().get(); - } - } catch (InvalidMessageException | InvalidVersionException e) { - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "WhisperMessage received"); - WhisperMessage message = new WhisperMessage(encryptedKey); - plaintext = cipher.decrypt(message); - } catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) { - Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage()); - } - } catch (LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException e) { - Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage()); - } - - if (plaintext != null) { - if (!status.isActive()) { - setTrust(status.toActive()); - } - } - } - return plaintext; - } - - @Nullable - public byte[] processSending(@NonNull byte[] outgoingMessage) { - FingerprintStatus status = getTrust(); - if (status.isTrustedAndActive()) { - CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage); - return ciphertextMessage.serialize(); - } else { - return null; - } - } + private final SessionCipher cipher; + private final SQLiteAxolotlStore sqLiteAxolotlStore; + private final AxolotlAddress remoteAddress; + private final Account account; + private IdentityKey identityKey; + private Integer preKeyId = null; + private boolean fresh = true; + + public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress, IdentityKey identityKey) { + this(account, store, remoteAddress); + this.identityKey = identityKey; + } + + public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress) { + this.cipher = new SessionCipher(store, remoteAddress); + this.remoteAddress = remoteAddress; + this.sqLiteAxolotlStore = store; + this.account = account; + } + + public Integer getPreKeyId() { + return preKeyId; + } + + public void resetPreKeyId() { + + preKeyId = null; + } + + public String getFingerprint() { + return identityKey == null ? null : identityKey.getFingerprint().replaceAll("\\s", ""); + } + + public IdentityKey getIdentityKey() { + return identityKey; + } + + public AxolotlAddress getRemoteAddress() { + return remoteAddress; + } + + public boolean isFresh() { + return fresh; + } + + public void setNotFresh() { + this.fresh = false; + } + + protected void setTrust(FingerprintStatus status) { + sqLiteAxolotlStore.setFingerprintStatus(getFingerprint(), status); + } + + public FingerprintStatus getTrust() { + FingerprintStatus status = sqLiteAxolotlStore.getFingerprintStatus(getFingerprint()); + return (status == null) ? FingerprintStatus.createActiveUndecided() : status; + } + + @Nullable + public byte[] processReceiving(byte[] encryptedKey) { + byte[] plaintext = null; + FingerprintStatus status = getTrust(); + if (!status.isCompromised()) { + try { + try { + PreKeyWhisperMessage message = new PreKeyWhisperMessage(encryptedKey); + if (!message.getPreKeyId().isPresent()) { + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage did not contain a PreKeyId"); + return null; + } + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId()); + IdentityKey msgIdentityKey = message.getIdentityKey(); + if (this.identityKey != null && !this.identityKey.equals(msgIdentityKey)) { + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Had session with fingerprint " + this.getFingerprint() + ", received message with fingerprint " + msgIdentityKey.getFingerprint()); + } else { + this.identityKey = msgIdentityKey; + plaintext = cipher.decrypt(message); + preKeyId = message.getPreKeyId().get(); + } + } catch (InvalidMessageException | InvalidVersionException e) { + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "WhisperMessage received"); + WhisperMessage message = new WhisperMessage(encryptedKey); + plaintext = cipher.decrypt(message); + } catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) { + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage()); + } + } catch (LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException e) { + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage()); + } + + if (plaintext != null) { + if (!status.isActive()) { + setTrust(status.toActive()); + } + } + } + return plaintext; + } + + @Nullable + public byte[] processSending(@NonNull byte[] outgoingMessage) { + FingerprintStatus status = getTrust(); + if (status.isTrustedAndActive()) { + CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage); + return ciphertextMessage.serialize(); + } else { + return null; + } + } } diff --git a/src/main/java/de/pixart/messenger/crypto/sasl/Anonymous.java b/src/main/java/de/pixart/messenger/crypto/sasl/Anonymous.java index 7e4e98bc6..93c3de64b 100644 --- a/src/main/java/de/pixart/messenger/crypto/sasl/Anonymous.java +++ b/src/main/java/de/pixart/messenger/crypto/sasl/Anonymous.java @@ -7,22 +7,22 @@ import de.pixart.messenger.xml.TagWriter; public class Anonymous extends SaslMechanism { - public Anonymous(TagWriter tagWriter, Account account, SecureRandom rng) { - super(tagWriter, account, rng); - } + public Anonymous(TagWriter tagWriter, Account account, SecureRandom rng) { + super(tagWriter, account, rng); + } - @Override - public int getPriority() { - return 0; - } + @Override + public int getPriority() { + return 0; + } - @Override - public String getMechanism() { - return "ANONYMOUS"; - } + @Override + public String getMechanism() { + return "ANONYMOUS"; + } - @Override - public String getClientFirstMessage() { - return ""; - } + @Override + public String getClientFirstMessage() { + return ""; + } } diff --git a/src/main/java/de/pixart/messenger/crypto/sasl/DigestMd5.java b/src/main/java/de/pixart/messenger/crypto/sasl/DigestMd5.java index 09ac4865a..e974d8cd4 100644 --- a/src/main/java/de/pixart/messenger/crypto/sasl/DigestMd5.java +++ b/src/main/java/de/pixart/messenger/crypto/sasl/DigestMd5.java @@ -13,79 +13,79 @@ import de.pixart.messenger.utils.CryptoHelper; import de.pixart.messenger.xml.TagWriter; public class DigestMd5 extends SaslMechanism { - public DigestMd5(final TagWriter tagWriter, final Account account, final SecureRandom rng) { - super(tagWriter, account, rng); - } + public DigestMd5(final TagWriter tagWriter, final Account account, final SecureRandom rng) { + super(tagWriter, account, rng); + } - @Override - public int getPriority() { - return 10; - } + @Override + public int getPriority() { + return 10; + } - @Override - public String getMechanism() { - return "DIGEST-MD5"; - } + @Override + public String getMechanism() { + return "DIGEST-MD5"; + } - private State state = State.INITIAL; + private State state = State.INITIAL; - @Override - public String getResponse(final String challenge) throws AuthenticationException { - switch (state) { - case INITIAL: - state = State.RESPONSE_SENT; - final String encodedResponse; - try { - final Tokenizer tokenizer = new Tokenizer(Base64.decode(challenge, Base64.DEFAULT)); - String nonce = ""; - for (final String token : tokenizer) { - final String[] parts = token.split("=", 2); - if (parts[0].equals("nonce")) { - nonce = parts[1].replace("\"", ""); - } else if (parts[0].equals("rspauth")) { - return ""; - } - } - final String digestUri = "xmpp/" + account.getServer(); - final String nonceCount = "00000001"; - final String x = account.getUsername() + ":" + account.getServer() + ":" - + account.getPassword(); - final MessageDigest md = MessageDigest.getInstance("MD5"); - final byte[] y = md.digest(x.getBytes(Charset.defaultCharset())); - final String cNonce = new BigInteger(100, rng).toString(32); - final byte[] a1 = CryptoHelper.concatenateByteArrays(y, - (":" + nonce + ":" + cNonce).getBytes(Charset.defaultCharset())); - final String a2 = "AUTHENTICATE:" + digestUri; - final String ha1 = CryptoHelper.bytesToHex(md.digest(a1)); - final String ha2 = CryptoHelper.bytesToHex(md.digest(a2.getBytes(Charset - .defaultCharset()))); - final String kd = ha1 + ":" + nonce + ":" + nonceCount + ":" + cNonce - + ":auth:" + ha2; - final String response = CryptoHelper.bytesToHex(md.digest(kd.getBytes(Charset - .defaultCharset()))); - final String saslString = "username=\"" + account.getUsername() - + "\",realm=\"" + account.getServer() + "\",nonce=\"" - + nonce + "\",cnonce=\"" + cNonce + "\",nc=" + nonceCount - + ",qop=auth,digest-uri=\"" + digestUri + "\",response=" - + response + ",charset=utf-8"; - encodedResponse = Base64.encodeToString( - saslString.getBytes(Charset.defaultCharset()), - Base64.NO_WRAP); - } catch (final NoSuchAlgorithmException e) { - throw new AuthenticationException(e); - } + @Override + public String getResponse(final String challenge) throws AuthenticationException { + switch (state) { + case INITIAL: + state = State.RESPONSE_SENT; + final String encodedResponse; + try { + final Tokenizer tokenizer = new Tokenizer(Base64.decode(challenge, Base64.DEFAULT)); + String nonce = ""; + for (final String token : tokenizer) { + final String[] parts = token.split("=", 2); + if (parts[0].equals("nonce")) { + nonce = parts[1].replace("\"", ""); + } else if (parts[0].equals("rspauth")) { + return ""; + } + } + final String digestUri = "xmpp/" + account.getServer(); + final String nonceCount = "00000001"; + final String x = account.getUsername() + ":" + account.getServer() + ":" + + account.getPassword(); + final MessageDigest md = MessageDigest.getInstance("MD5"); + final byte[] y = md.digest(x.getBytes(Charset.defaultCharset())); + final String cNonce = new BigInteger(100, rng).toString(32); + final byte[] a1 = CryptoHelper.concatenateByteArrays(y, + (":" + nonce + ":" + cNonce).getBytes(Charset.defaultCharset())); + final String a2 = "AUTHENTICATE:" + digestUri; + final String ha1 = CryptoHelper.bytesToHex(md.digest(a1)); + final String ha2 = CryptoHelper.bytesToHex(md.digest(a2.getBytes(Charset + .defaultCharset()))); + final String kd = ha1 + ":" + nonce + ":" + nonceCount + ":" + cNonce + + ":auth:" + ha2; + final String response = CryptoHelper.bytesToHex(md.digest(kd.getBytes(Charset + .defaultCharset()))); + final String saslString = "username=\"" + account.getUsername() + + "\",realm=\"" + account.getServer() + "\",nonce=\"" + + nonce + "\",cnonce=\"" + cNonce + "\",nc=" + nonceCount + + ",qop=auth,digest-uri=\"" + digestUri + "\",response=" + + response + ",charset=utf-8"; + encodedResponse = Base64.encodeToString( + saslString.getBytes(Charset.defaultCharset()), + Base64.NO_WRAP); + } catch (final NoSuchAlgorithmException e) { + throw new AuthenticationException(e); + } - return encodedResponse; - case RESPONSE_SENT: - state = State.VALID_SERVER_RESPONSE; - break; - case VALID_SERVER_RESPONSE: - if (challenge==null) { - return null; //everything is fine - } - default: - throw new InvalidStateException(state); - } - return null; - } + return encodedResponse; + case RESPONSE_SENT: + state = State.VALID_SERVER_RESPONSE; + break; + case VALID_SERVER_RESPONSE: + if (challenge == null) { + return null; //everything is fine + } + default: + throw new InvalidStateException(state); + } + return null; + } } diff --git a/src/main/java/de/pixart/messenger/crypto/sasl/External.java b/src/main/java/de/pixart/messenger/crypto/sasl/External.java index a1a79a0a8..b80d174f0 100644 --- a/src/main/java/de/pixart/messenger/crypto/sasl/External.java +++ b/src/main/java/de/pixart/messenger/crypto/sasl/External.java @@ -9,22 +9,22 @@ import de.pixart.messenger.xml.TagWriter; public class External extends SaslMechanism { - public External(TagWriter tagWriter, Account account, SecureRandom rng) { - super(tagWriter, account, rng); - } - - @Override - public int getPriority() { - return 25; - } - - @Override - public String getMechanism() { - return "EXTERNAL"; - } - - @Override - public String getClientFirstMessage() { - return Base64.encodeToString(account.getJid().toBareJid().toString().getBytes(),Base64.NO_WRAP); - } + public External(TagWriter tagWriter, Account account, SecureRandom rng) { + super(tagWriter, account, rng); + } + + @Override + public int getPriority() { + return 25; + } + + @Override + public String getMechanism() { + return "EXTERNAL"; + } + + @Override + public String getClientFirstMessage() { + return Base64.encodeToString(account.getJid().toBareJid().toString().getBytes(), Base64.NO_WRAP); + } } diff --git a/src/main/java/de/pixart/messenger/crypto/sasl/Plain.java b/src/main/java/de/pixart/messenger/crypto/sasl/Plain.java index d0aa90b49..27de45247 100644 --- a/src/main/java/de/pixart/messenger/crypto/sasl/Plain.java +++ b/src/main/java/de/pixart/messenger/crypto/sasl/Plain.java @@ -8,23 +8,23 @@ import de.pixart.messenger.entities.Account; import de.pixart.messenger.xml.TagWriter; public class Plain extends SaslMechanism { - public Plain(final TagWriter tagWriter, final Account account) { - super(tagWriter, account, null); - } + public Plain(final TagWriter tagWriter, final Account account) { + super(tagWriter, account, null); + } - @Override - public int getPriority() { - return 10; - } + @Override + public int getPriority() { + return 10; + } - @Override - public String getMechanism() { - return "PLAIN"; - } + @Override + public String getMechanism() { + return "PLAIN"; + } - @Override - public String getClientFirstMessage() { - final String sasl = '\u0000' + account.getUsername() + '\u0000' + account.getPassword(); - return Base64.encodeToString(sasl.getBytes(Charset.defaultCharset()), Base64.NO_WRAP); - } + @Override + public String getClientFirstMessage() { + final String sasl = '\u0000' + account.getUsername() + '\u0000' + account.getPassword(); + return Base64.encodeToString(sasl.getBytes(Charset.defaultCharset()), Base64.NO_WRAP); + } } diff --git a/src/main/java/de/pixart/messenger/crypto/sasl/SaslMechanism.java b/src/main/java/de/pixart/messenger/crypto/sasl/SaslMechanism.java index 19e8f3591..8b8883b9f 100644 --- a/src/main/java/de/pixart/messenger/crypto/sasl/SaslMechanism.java +++ b/src/main/java/de/pixart/messenger/crypto/sasl/SaslMechanism.java @@ -7,56 +7,59 @@ import de.pixart.messenger.xml.TagWriter; public abstract class SaslMechanism { - final protected TagWriter tagWriter; - final protected Account account; - final protected SecureRandom rng; - - protected enum State { - INITIAL, - AUTH_TEXT_SENT, - RESPONSE_SENT, - VALID_SERVER_RESPONSE, - } - - public static class AuthenticationException extends Exception { - public AuthenticationException(final String message) { - super(message); - } - - public AuthenticationException(final Exception inner) { - super(inner); - } - } - - public static class InvalidStateException extends AuthenticationException { - public InvalidStateException(final String message) { - super(message); - } - - public InvalidStateException(final State state) { - this("Invalid state: " + state.toString()); - } - } - - public SaslMechanism(final TagWriter tagWriter, final Account account, final SecureRandom rng) { - this.tagWriter = tagWriter; - this.account = account; - this.rng = rng; - } - - /** - * The priority is used to pin the authentication mechanism. If authentication fails, it MAY be retried with another - * mechanism of the same priority, but MUST NOT be tried with a mechanism of lower priority (to prevent downgrade - * attacks). - * @return An arbitrary int representing the priority - */ - public abstract int getPriority(); - - public abstract String getMechanism(); - public String getClientFirstMessage() { - return ""; - } - public String getResponse(final String challenge) throws AuthenticationException { - return ""; - } + final protected TagWriter tagWriter; + final protected Account account; + final protected SecureRandom rng; + + protected enum State { + INITIAL, + AUTH_TEXT_SENT, + RESPONSE_SENT, + VALID_SERVER_RESPONSE, + } + + public static class AuthenticationException extends Exception { + public AuthenticationException(final String message) { + super(message); + } + + public AuthenticationException(final Exception inner) { + super(inner); + } + } + + public static class InvalidStateException extends AuthenticationException { + public InvalidStateException(final String message) { + super(message); + } + + public InvalidStateException(final State state) { + this("Invalid state: " + state.toString()); + } + } + + public SaslMechanism(final TagWriter tagWriter, final Account account, final SecureRandom rng) { + this.tagWriter = tagWriter; + this.account = account; + this.rng = rng; + } + + /** + * The priority is used to pin the authentication mechanism. If authentication fails, it MAY be retried with another + * mechanism of the same priority, but MUST NOT be tried with a mechanism of lower priority (to prevent downgrade + * attacks). + * + * @return An arbitrary int representing the priority + */ + public abstract int getPriority(); + + public abstract String getMechanism(); + + public String getClientFirstMessage() { + return ""; + } + + public String getResponse(final String challenge) throws AuthenticationException { + return ""; + } } diff --git a/src/main/java/de/pixart/messenger/crypto/sasl/ScramSha1.java b/src/main/java/de/pixart/messenger/crypto/sasl/ScramSha1.java index abff542d4..d21cad37d 100644 --- a/src/main/java/de/pixart/messenger/crypto/sasl/ScramSha1.java +++ b/src/main/java/de/pixart/messenger/crypto/sasl/ScramSha1.java @@ -18,221 +18,222 @@ import de.pixart.messenger.utils.CryptoHelper; import de.pixart.messenger.xml.TagWriter; public class ScramSha1 extends SaslMechanism { - // TODO: When channel binding (SCRAM-SHA1-PLUS) is supported in future, generalize this to indicate support and/or usage. - final private static String GS2_HEADER = "n,,"; - private String clientFirstMessageBare; - final private String clientNonce; - private byte[] serverSignature = null; - private static HMac HMAC; - private static Digest DIGEST; - private static final byte[] CLIENT_KEY_BYTES = "Client Key".getBytes(); - private static final byte[] SERVER_KEY_BYTES = "Server Key".getBytes(); - - public static class KeyPair { - final public byte[] clientKey; - final public byte[] serverKey; - - public KeyPair(final byte[] clientKey, final byte[] serverKey) { - this.clientKey = clientKey; - this.serverKey = serverKey; - } - } - - private static final LruCache CACHE; - - static { - DIGEST = new SHA1Digest(); - HMAC = new HMac(new SHA1Digest()); - CACHE = new LruCache(10) { - protected KeyPair create(final String k) { - // Map keys are "bytesToHex(JID),bytesToHex(password),bytesToHex(salt),iterations". - // Changing any of these values forces a cache miss. `CryptoHelper.bytesToHex()' - // is applied to prevent commas in the strings breaking things. - final String[] kparts = k.split(",", 4); - try { - final byte[] saltedPassword, serverKey, clientKey; - saltedPassword = hi(CryptoHelper.hexToString(kparts[1]).getBytes(), - Base64.decode(CryptoHelper.hexToString(kparts[2]), Base64.DEFAULT), Integer.valueOf(kparts[3])); - serverKey = hmac(saltedPassword, SERVER_KEY_BYTES); - clientKey = hmac(saltedPassword, CLIENT_KEY_BYTES); - - return new KeyPair(clientKey, serverKey); - } catch (final InvalidKeyException | NumberFormatException e) { - return null; - } - } - }; - } - - private State state = State.INITIAL; - - public ScramSha1(final TagWriter tagWriter, final Account account, final SecureRandom rng) { - super(tagWriter, account, rng); - - // This nonce should be different for each authentication attempt. - clientNonce = new BigInteger(100, this.rng).toString(32); - clientFirstMessageBare = ""; - } - - @Override - public int getPriority() { - return 20; - } - - @Override - public String getMechanism() { - return "SCRAM-SHA-1"; - } - - @Override - public String getClientFirstMessage() { - if (clientFirstMessageBare.isEmpty() && state == State.INITIAL) { - clientFirstMessageBare = "n=" + CryptoHelper.saslEscape(CryptoHelper.saslPrep(account.getUsername())) + - ",r=" + this.clientNonce; - state = State.AUTH_TEXT_SENT; - } - return Base64.encodeToString( - (GS2_HEADER + clientFirstMessageBare).getBytes(Charset.defaultCharset()), - Base64.NO_WRAP); - } - - @Override - public String getResponse(final String challenge) throws AuthenticationException { - switch (state) { - case AUTH_TEXT_SENT: - if (challenge == null) { - throw new AuthenticationException("challenge can not be null"); - } - byte[] serverFirstMessage = Base64.decode(challenge, Base64.DEFAULT); - final Tokenizer tokenizer = new Tokenizer(serverFirstMessage); - String nonce = ""; - int iterationCount = -1; - String salt = ""; - for (final String token : tokenizer) { - if (token.charAt(1) == '=') { - switch (token.charAt(0)) { - case 'i': - try { - iterationCount = Integer.parseInt(token.substring(2)); - } catch (final NumberFormatException e) { - throw new AuthenticationException(e); - } - break; - case 's': - salt = token.substring(2); - break; - case 'r': - nonce = token.substring(2); - break; - case 'm': - /* + // TODO: When channel binding (SCRAM-SHA1-PLUS) is supported in future, generalize this to indicate support and/or usage. + final private static String GS2_HEADER = "n,,"; + private String clientFirstMessageBare; + final private String clientNonce; + private byte[] serverSignature = null; + private static HMac HMAC; + private static Digest DIGEST; + private static final byte[] CLIENT_KEY_BYTES = "Client Key".getBytes(); + private static final byte[] SERVER_KEY_BYTES = "Server Key".getBytes(); + + public static class KeyPair { + final public byte[] clientKey; + final public byte[] serverKey; + + public KeyPair(final byte[] clientKey, final byte[] serverKey) { + this.clientKey = clientKey; + this.serverKey = serverKey; + } + } + + private static final LruCache CACHE; + + static { + DIGEST = new SHA1Digest(); + HMAC = new HMac(new SHA1Digest()); + CACHE = new LruCache(10) { + protected KeyPair create(final String k) { + // Map keys are "bytesToHex(JID),bytesToHex(password),bytesToHex(salt),iterations". + // Changing any of these values forces a cache miss. `CryptoHelper.bytesToHex()' + // is applied to prevent commas in the strings breaking things. + final String[] kparts = k.split(",", 4); + try { + final byte[] saltedPassword, serverKey, clientKey; + saltedPassword = hi(CryptoHelper.hexToString(kparts[1]).getBytes(), + Base64.decode(CryptoHelper.hexToString(kparts[2]), Base64.DEFAULT), Integer.valueOf(kparts[3])); + serverKey = hmac(saltedPassword, SERVER_KEY_BYTES); + clientKey = hmac(saltedPassword, CLIENT_KEY_BYTES); + + return new KeyPair(clientKey, serverKey); + } catch (final InvalidKeyException | NumberFormatException e) { + return null; + } + } + }; + } + + private State state = State.INITIAL; + + public ScramSha1(final TagWriter tagWriter, final Account account, final SecureRandom rng) { + super(tagWriter, account, rng); + + // This nonce should be different for each authentication attempt. + clientNonce = new BigInteger(100, this.rng).toString(32); + clientFirstMessageBare = ""; + } + + @Override + public int getPriority() { + return 20; + } + + @Override + public String getMechanism() { + return "SCRAM-SHA-1"; + } + + @Override + public String getClientFirstMessage() { + if (clientFirstMessageBare.isEmpty() && state == State.INITIAL) { + clientFirstMessageBare = "n=" + CryptoHelper.saslEscape(CryptoHelper.saslPrep(account.getUsername())) + + ",r=" + this.clientNonce; + state = State.AUTH_TEXT_SENT; + } + return Base64.encodeToString( + (GS2_HEADER + clientFirstMessageBare).getBytes(Charset.defaultCharset()), + Base64.NO_WRAP); + } + + @Override + public String getResponse(final String challenge) throws AuthenticationException { + switch (state) { + case AUTH_TEXT_SENT: + if (challenge == null) { + throw new AuthenticationException("challenge can not be null"); + } + byte[] serverFirstMessage = Base64.decode(challenge, Base64.DEFAULT); + final Tokenizer tokenizer = new Tokenizer(serverFirstMessage); + String nonce = ""; + int iterationCount = -1; + String salt = ""; + for (final String token : tokenizer) { + if (token.charAt(1) == '=') { + switch (token.charAt(0)) { + case 'i': + try { + iterationCount = Integer.parseInt(token.substring(2)); + } catch (final NumberFormatException e) { + throw new AuthenticationException(e); + } + break; + case 's': + salt = token.substring(2); + break; + case 'r': + nonce = token.substring(2); + break; + case 'm': + /* * RFC 5802: * m: This attribute is reserved for future extensibility. In this * version of SCRAM, its presence in a client or a server message * MUST cause authentication failure when the attribute is parsed by * the other end. */ - throw new AuthenticationException("Server sent reserved token: `m'"); - } - } - } - - if (iterationCount < 0) { - throw new AuthenticationException("Server did not send iteration count"); - } - if (nonce.isEmpty() || !nonce.startsWith(clientNonce)) { - throw new AuthenticationException("Server nonce does not contain client nonce: " + nonce); - } - if (salt.isEmpty()) { - throw new AuthenticationException("Server sent empty salt"); - } - - final String clientFinalMessageWithoutProof = "c=" + Base64.encodeToString( - GS2_HEADER.getBytes(), Base64.NO_WRAP) + ",r=" + nonce; - final byte[] authMessage = (clientFirstMessageBare + ',' + new String(serverFirstMessage) + ',' - + clientFinalMessageWithoutProof).getBytes(); - - // Map keys are "bytesToHex(JID),bytesToHex(password),bytesToHex(salt),iterations". - final KeyPair keys = CACHE.get( - CryptoHelper.bytesToHex(account.getJid().toBareJid().toString().getBytes()) + "," - + CryptoHelper.bytesToHex(account.getPassword().getBytes()) + "," - + CryptoHelper.bytesToHex(salt.getBytes()) + "," - + String.valueOf(iterationCount) - ); - if (keys == null) { - throw new AuthenticationException("Invalid keys generated"); - } - final byte[] clientSignature; - try { - serverSignature = hmac(keys.serverKey, authMessage); - final byte[] storedKey = digest(keys.clientKey); - - clientSignature = hmac(storedKey, authMessage); - - } catch (final InvalidKeyException e) { - throw new AuthenticationException(e); - } - - final byte[] clientProof = new byte[keys.clientKey.length]; - - for (int i = 0; i < clientProof.length; i++) { - clientProof[i] = (byte) (keys.clientKey[i] ^ clientSignature[i]); - } - - - final String clientFinalMessage = clientFinalMessageWithoutProof + ",p=" + - Base64.encodeToString(clientProof, Base64.NO_WRAP); - state = State.RESPONSE_SENT; - return Base64.encodeToString(clientFinalMessage.getBytes(), Base64.NO_WRAP); - case RESPONSE_SENT: - try { - final String clientCalculatedServerFinalMessage = "v=" + - Base64.encodeToString(serverSignature, Base64.NO_WRAP); - if (!clientCalculatedServerFinalMessage.equals(new String(Base64.decode(challenge, Base64.DEFAULT)))) { - throw new Exception(); - }; - state = State.VALID_SERVER_RESPONSE; - return ""; - } catch(Exception e) { - throw new AuthenticationException("Server final message does not match calculated final message"); - } - default: - throw new InvalidStateException(state); - } - } - - public static synchronized byte[] hmac(final byte[] key, final byte[] input) - throws InvalidKeyException { - HMAC.init(new KeyParameter(key)); - HMAC.update(input, 0, input.length); - final byte[] out = new byte[HMAC.getMacSize()]; - HMAC.doFinal(out, 0); - return out; - } - - public static synchronized byte[] digest(byte[] bytes) { - DIGEST.reset(); - DIGEST.update(bytes, 0, bytes.length); - final byte[] out = new byte[DIGEST.getDigestSize()]; - DIGEST.doFinal(out, 0); - return out; - } - - /* - * Hi() is, essentially, PBKDF2 [RFC2898] with HMAC() as the - * pseudorandom function (PRF) and with dkLen == output length of - * HMAC() == output length of H(). - */ - private static synchronized byte[] hi(final byte[] key, final byte[] salt, final int iterations) - throws InvalidKeyException { - byte[] u = hmac(key, CryptoHelper.concatenateByteArrays(salt, CryptoHelper.ONE)); - byte[] out = u.clone(); - for (int i = 1; i < iterations; i++) { - u = hmac(key, u); - for (int j = 0; j < u.length; j++) { - out[j] ^= u[j]; - } - } - return out; - } + throw new AuthenticationException("Server sent reserved token: `m'"); + } + } + } + + if (iterationCount < 0) { + throw new AuthenticationException("Server did not send iteration count"); + } + if (nonce.isEmpty() || !nonce.startsWith(clientNonce)) { + throw new AuthenticationException("Server nonce does not contain client nonce: " + nonce); + } + if (salt.isEmpty()) { + throw new AuthenticationException("Server sent empty salt"); + } + + final String clientFinalMessageWithoutProof = "c=" + Base64.encodeToString( + GS2_HEADER.getBytes(), Base64.NO_WRAP) + ",r=" + nonce; + final byte[] authMessage = (clientFirstMessageBare + ',' + new String(serverFirstMessage) + ',' + + clientFinalMessageWithoutProof).getBytes(); + + // Map keys are "bytesToHex(JID),bytesToHex(password),bytesToHex(salt),iterations". + final KeyPair keys = CACHE.get( + CryptoHelper.bytesToHex(account.getJid().toBareJid().toString().getBytes()) + "," + + CryptoHelper.bytesToHex(account.getPassword().getBytes()) + "," + + CryptoHelper.bytesToHex(salt.getBytes()) + "," + + String.valueOf(iterationCount) + ); + if (keys == null) { + throw new AuthenticationException("Invalid keys generated"); + } + final byte[] clientSignature; + try { + serverSignature = hmac(keys.serverKey, authMessage); + final byte[] storedKey = digest(keys.clientKey); + + clientSignature = hmac(storedKey, authMessage); + + } catch (final InvalidKeyException e) { + throw new AuthenticationException(e); + } + + final byte[] clientProof = new byte[keys.clientKey.length]; + + for (int i = 0; i < clientProof.length; i++) { + clientProof[i] = (byte) (keys.clientKey[i] ^ clientSignature[i]); + } + + + final String clientFinalMessage = clientFinalMessageWithoutProof + ",p=" + + Base64.encodeToString(clientProof, Base64.NO_WRAP); + state = State.RESPONSE_SENT; + return Base64.encodeToString(clientFinalMessage.getBytes(), Base64.NO_WRAP); + case RESPONSE_SENT: + try { + final String clientCalculatedServerFinalMessage = "v=" + + Base64.encodeToString(serverSignature, Base64.NO_WRAP); + if (!clientCalculatedServerFinalMessage.equals(new String(Base64.decode(challenge, Base64.DEFAULT)))) { + throw new Exception(); + } + ; + state = State.VALID_SERVER_RESPONSE; + return ""; + } catch (Exception e) { + throw new AuthenticationException("Server final message does not match calculated final message"); + } + default: + throw new InvalidStateException(state); + } + } + + public static synchronized byte[] hmac(final byte[] key, final byte[] input) + throws InvalidKeyException { + HMAC.init(new KeyParameter(key)); + HMAC.update(input, 0, input.length); + final byte[] out = new byte[HMAC.getMacSize()]; + HMAC.doFinal(out, 0); + return out; + } + + public static synchronized byte[] digest(byte[] bytes) { + DIGEST.reset(); + DIGEST.update(bytes, 0, bytes.length); + final byte[] out = new byte[DIGEST.getDigestSize()]; + DIGEST.doFinal(out, 0); + return out; + } + + /* + * Hi() is, essentially, PBKDF2 [RFC2898] with HMAC() as the + * pseudorandom function (PRF) and with dkLen == output length of + * HMAC() == output length of H(). + */ + private static synchronized byte[] hi(final byte[] key, final byte[] salt, final int iterations) + throws InvalidKeyException { + byte[] u = hmac(key, CryptoHelper.concatenateByteArrays(salt, CryptoHelper.ONE)); + byte[] out = u.clone(); + for (int i = 1; i < iterations; i++) { + u = hmac(key, u); + for (int j = 0; j < u.length; j++) { + out[j] ^= u[j]; + } + } + return out; + } } diff --git a/src/main/java/de/pixart/messenger/crypto/sasl/Tokenizer.java b/src/main/java/de/pixart/messenger/crypto/sasl/Tokenizer.java index 01cd07929..67e4418dd 100644 --- a/src/main/java/de/pixart/messenger/crypto/sasl/Tokenizer.java +++ b/src/main/java/de/pixart/messenger/crypto/sasl/Tokenizer.java @@ -10,69 +10,69 @@ import java.util.NoSuchElementException; * A tokenizer for GS2 header strings */ public final class Tokenizer implements Iterator, Iterable { - private final List parts; - private int index; + private final List parts; + private int index; - public Tokenizer(final byte[] challenge) { - final String challengeString = new String(challenge); - parts = new ArrayList<>(Arrays.asList(challengeString.split(","))); - // Trim parts. - for (int i = 0; i < parts.size(); i++) { - parts.set(i, parts.get(i).trim()); - } - index = 0; - } + public Tokenizer(final byte[] challenge) { + final String challengeString = new String(challenge); + parts = new ArrayList<>(Arrays.asList(challengeString.split(","))); + // Trim parts. + for (int i = 0; i < parts.size(); i++) { + parts.set(i, parts.get(i).trim()); + } + index = 0; + } - /** - * Returns true if there is at least one more element, false otherwise. - * - * @see #next - */ - @Override - public boolean hasNext() { - return parts.size() != index + 1; - } + /** + * Returns true if there is at least one more element, false otherwise. + * + * @see #next + */ + @Override + public boolean hasNext() { + return parts.size() != index + 1; + } - /** - * Returns the next object and advances the iterator. - * - * @return the next object. - * @throws java.util.NoSuchElementException if there are no more elements. - * @see #hasNext - */ - @Override - public String next() { - if (hasNext()) { - return parts.get(index++); - } else { - throw new NoSuchElementException("No such element. Size is: " + parts.size()); - } - } + /** + * Returns the next object and advances the iterator. + * + * @return the next object. + * @throws java.util.NoSuchElementException if there are no more elements. + * @see #hasNext + */ + @Override + public String next() { + if (hasNext()) { + return parts.get(index++); + } else { + throw new NoSuchElementException("No such element. Size is: " + parts.size()); + } + } - /** - * Removes the last object returned by {@code next} from the collection. - * This method can only be called once between each call to {@code next}. - * - * @throws UnsupportedOperationException if removing is not supported by the collection being - * iterated. - * @throws IllegalStateException if {@code next} has not been called, or {@code remove} has - * already been called after the last call to {@code next}. - */ - @Override - public void remove() { - if(index <= 0) { - throw new IllegalStateException("You can't delete an element before first next() method call"); - } - parts.remove(--index); - } + /** + * Removes the last object returned by {@code next} from the collection. + * This method can only be called once between each call to {@code next}. + * + * @throws UnsupportedOperationException if removing is not supported by the collection being + * iterated. + * @throws IllegalStateException if {@code next} has not been called, or {@code remove} has + * already been called after the last call to {@code next}. + */ + @Override + public void remove() { + if (index <= 0) { + throw new IllegalStateException("You can't delete an element before first next() method call"); + } + parts.remove(--index); + } - /** - * Returns an {@link java.util.Iterator} for the elements in this object. - * - * @return An {@code Iterator} instance. - */ - @Override - public Iterator iterator() { - return parts.iterator(); - } + /** + * Returns an {@link java.util.Iterator} for the elements in this object. + * + * @return An {@code Iterator} instance. + */ + @Override + public Iterator iterator() { + return parts.iterator(); + } } diff --git a/src/main/java/de/pixart/messenger/entities/AbstractEntity.java b/src/main/java/de/pixart/messenger/entities/AbstractEntity.java index 1194cb819..2cac3405e 100644 --- a/src/main/java/de/pixart/messenger/entities/AbstractEntity.java +++ b/src/main/java/de/pixart/messenger/entities/AbstractEntity.java @@ -4,17 +4,17 @@ import android.content.ContentValues; public abstract class AbstractEntity { - public static final String UUID = "uuid"; + public static final String UUID = "uuid"; - protected String uuid; + protected String uuid; - public String getUuid() { - return this.uuid; - } + public String getUuid() { + return this.uuid; + } - public abstract ContentValues getContentValues(); + public abstract ContentValues getContentValues(); - public boolean equals(AbstractEntity entity) { - return this.getUuid().equals(entity.getUuid()); - } + public boolean equals(AbstractEntity entity) { + return this.getUuid().equals(entity.getUuid()); + } } diff --git a/src/main/java/de/pixart/messenger/entities/Account.java b/src/main/java/de/pixart/messenger/entities/Account.java index 7a94750ab..1b349e526 100644 --- a/src/main/java/de/pixart/messenger/entities/Account.java +++ b/src/main/java/de/pixart/messenger/entities/Account.java @@ -33,633 +33,633 @@ import de.pixart.messenger.xmpp.jid.Jid; public class Account extends AbstractEntity { - public static final String TABLENAME = "accounts"; - - public static final String USERNAME = "username"; - public static final String SERVER = "server"; - public static final String PASSWORD = "password"; - public static final String OPTIONS = "options"; - public static final String ROSTERVERSION = "rosterversion"; - public static final String KEYS = "keys"; - public static final String AVATAR = "avatar"; - public static final String DISPLAY_NAME = "display_name"; - public static final String HOSTNAME = "hostname"; - public static final String PORT = "port"; - public static final String STATUS = "status"; - public static final String STATUS_MESSAGE = "status_message"; - - public static final String PINNED_MECHANISM_KEY = "pinned_mechanism"; - - public static final int OPTION_USETLS = 0; - public static final int OPTION_DISABLED = 1; - public static final int OPTION_REGISTER = 2; - public static final int OPTION_USECOMPRESSION = 3; - public static final int OPTION_MAGIC_CREATE = 4; - public final HashSet> inProgressDiscoFetches = new HashSet<>(); - - public boolean httpUploadAvailable(long filesize) { - return xmppConnection != null && xmppConnection.getFeatures().httpUpload(filesize); - } - - public boolean httpUploadAvailable() { - return httpUploadAvailable(0); - } - - public void setDisplayName(String displayName) { - this.displayName = displayName; - } - - public String getDisplayName() { - return displayName; - } - - public XmppConnection.Identity getServerIdentity() { - if (xmppConnection == null) { - return XmppConnection.Identity.UNKNOWN; - } else { - return xmppConnection.getServerIdentity(); - } - } - - public Contact getSelfContact() { - return getRoster().getContact(jid); - } - - public boolean hasPendingPgpIntent(Conversation conversation) { - return pgpDecryptionService != null && pgpDecryptionService.hasPendingIntent(conversation); - } - - public boolean isPgpDecryptionServiceConnected() { - return pgpDecryptionService != null && pgpDecryptionService.isConnected(); - } - - public boolean setShowErrorNotification(boolean newValue) { - boolean oldValue = showErrorNotification(); - setKey("show_error",Boolean.toString(newValue)); - return newValue != oldValue; - } - - public boolean showErrorNotification() { - String key = getKey("show_error"); - return key == null || Boolean.parseBoolean(key); - } - - public enum State { - DISABLED, - OFFLINE, - CONNECTING, - ONLINE, - NO_INTERNET, - UNAUTHORIZED(true), - SERVER_NOT_FOUND(true), - REGISTRATION_FAILED(true), - REGISTRATION_CONFLICT(true), - REGISTRATION_SUCCESSFUL, - REGISTRATION_NOT_SUPPORTED(true), - SECURITY_ERROR(true), - INCOMPATIBLE_SERVER(true), - TOR_NOT_AVAILABLE(true), - BIND_FAILURE(true), - HOST_UNKNOWN(true), - REGISTRATION_PLEASE_WAIT(true), - STREAM_ERROR(true), - POLICY_VIOLATION(true), - REGISTRATION_PASSWORD_TOO_WEAK(true), - PAYMENT_REQUIRED(true), - MISSING_INTERNET_PERMISSION(true); - - private final boolean isError; - - public boolean isError() { - return this.isError; - } - - State(final boolean isError) { - this.isError = isError; - } - - State() { - this(false); - } - - public int getReadableId() { - switch (this) { - case DISABLED: - return R.string.account_status_disabled; - case ONLINE: - return R.string.account_status_online; - case CONNECTING: - return R.string.account_status_connecting; - case OFFLINE: - return R.string.account_status_offline; - case UNAUTHORIZED: - return R.string.account_status_unauthorized; - case SERVER_NOT_FOUND: - return R.string.account_status_not_found; - case NO_INTERNET: - return R.string.account_status_no_internet; - case REGISTRATION_FAILED: - return R.string.account_status_regis_fail; - case REGISTRATION_CONFLICT: - return R.string.account_status_regis_conflict; - case REGISTRATION_SUCCESSFUL: - return R.string.account_status_regis_success; - case REGISTRATION_NOT_SUPPORTED: - return R.string.account_status_regis_not_sup; - case SECURITY_ERROR: - return R.string.account_status_security_error; - case INCOMPATIBLE_SERVER: - return R.string.account_status_incompatible_server; - case TOR_NOT_AVAILABLE: - return R.string.account_status_tor_unavailable; - case BIND_FAILURE: - return R.string.account_status_bind_failure; - case HOST_UNKNOWN: - return R.string.account_status_host_unknown; - case POLICY_VIOLATION: - return R.string.account_status_policy_violation; - case REGISTRATION_PLEASE_WAIT: - return R.string.registration_please_wait; - case REGISTRATION_PASSWORD_TOO_WEAK: - return R.string.registration_password_too_weak; - case STREAM_ERROR: - return R.string.account_status_stream_error; - case PAYMENT_REQUIRED: - return R.string.payment_required; - case MISSING_INTERNET_PERMISSION: - return R.string.missing_internet_permission; - default: - return R.string.account_status_unknown; - } - } - } - - public List pendingConferenceJoins = new CopyOnWriteArrayList<>(); - public List pendingConferenceLeaves = new CopyOnWriteArrayList<>(); - - private static final String KEY_PGP_SIGNATURE = "pgp_signature"; - private static final String KEY_PGP_ID = "pgp_id"; - - protected Jid jid; - protected String password; - protected int options = 0; - protected String rosterVersion; - protected State status = State.OFFLINE; - protected final JSONObject keys; - protected String avatar; - protected String displayName = null; - protected String hostname = null; - protected int port = 5222; - protected boolean online = false; - private OtrService mOtrService = null; - private AxolotlService axolotlService = null; - private PgpDecryptionService pgpDecryptionService = null; - private XmppConnection xmppConnection = null; - private long mEndGracePeriod = 0L; - private String otrFingerprint; - private final Roster roster = new Roster(this); - private List bookmarks = new CopyOnWriteArrayList<>(); - private final Collection blocklist = new CopyOnWriteArraySet<>(); - private Presence.Status presenceStatus = Presence.Status.ONLINE; - private String presenceStatusMessage = null; - - public Account(final Jid jid, final String password) { - this(java.util.UUID.randomUUID().toString(), jid, - password, 0, null, "", null, null, null, 5222, Presence.Status.ONLINE, null); - } - - private Account(final String uuid, final Jid jid, - final String password, final int options, final String rosterVersion, final String keys, - final String avatar, String displayName, String hostname, int port, - final Presence.Status status, String statusMessage) { - this.uuid = uuid; - this.jid = jid; - if (jid.isBareJid()) { - this.setResource("mobile"); - } - this.password = password; - this.options = options; - this.rosterVersion = rosterVersion; - JSONObject tmp; - try { - tmp = new JSONObject(keys); - } catch (JSONException e) { - tmp = new JSONObject(); - } - this.keys = tmp; - this.avatar = avatar; - this.displayName = displayName; - this.hostname = hostname; - this.port = port; - this.presenceStatus = status; - this.presenceStatusMessage = statusMessage; - } - - public static Account fromCursor(final Cursor cursor) { - Jid jid = null; - try { - jid = Jid.fromParts(cursor.getString(cursor.getColumnIndex(USERNAME)), - cursor.getString(cursor.getColumnIndex(SERVER)), "mobile"); - } catch (final InvalidJidException ignored) { - } - return new Account(cursor.getString(cursor.getColumnIndex(UUID)), - jid, - cursor.getString(cursor.getColumnIndex(PASSWORD)), - cursor.getInt(cursor.getColumnIndex(OPTIONS)), - cursor.getString(cursor.getColumnIndex(ROSTERVERSION)), - cursor.getString(cursor.getColumnIndex(KEYS)), - cursor.getString(cursor.getColumnIndex(AVATAR)), - cursor.getString(cursor.getColumnIndex(DISPLAY_NAME)), - cursor.getString(cursor.getColumnIndex(HOSTNAME)), - cursor.getInt(cursor.getColumnIndex(PORT)), - Presence.Status.fromShowString(cursor.getString(cursor.getColumnIndex(STATUS))), - cursor.getString(cursor.getColumnIndex(STATUS_MESSAGE))); - } - - public boolean isOptionSet(final int option) { - return ((options & (1 << option)) != 0); - } - - public void setOption(final int option, final boolean value) { - if (value) { - this.options |= 1 << option; - } else { - this.options &= ~(1 << option); - } - } - - public String getUsername() { - return jid.getLocalpart(); - } - - public boolean setJid(final Jid next) { - final Jid prev = this.jid != null ? this.jid.toBareJid() : null; - this.jid = next; - return prev == null || (next != null && !prev.equals(next.toBareJid()) - ); - } - - public Jid getServer() { - return jid.toDomainJid(); - } - - public String getPassword() { - return password; - } - - public void setPassword(final String password) { - this.password = password; - } - - public void setHostname(String hostname) { - this.hostname = hostname; - } - - public String getHostname() { - return this.hostname == null ? "" : this.hostname; - } - - public boolean isOnion() { - final Jid server = getServer(); - return server != null && server.toString().toLowerCase().endsWith(".onion"); - } - - public void setPort(int port) { - this.port = port; - } - - public int getPort() { - return this.port; - } - - public State getStatus() { - if (isOptionSet(OPTION_DISABLED)) { - return State.DISABLED; - } else { - return this.status; - } - } - - public void setStatus(final State status) { - this.status = status; - } - - public boolean errorStatus() { - return getStatus().isError(); - } - - public boolean hasErrorStatus() { - return getXmppConnection() != null - && (getStatus().isError() || getStatus() == State.CONNECTING) - && getXmppConnection().getAttempt() >= 3; - } - - public void setPresenceStatus(Presence.Status status) { - this.presenceStatus = status; - } - - public Presence.Status getPresenceStatus() { - return this.presenceStatus; - } - - public void setPresenceStatusMessage(String message) { - this.presenceStatusMessage = message; - } - - public String getPresenceStatusMessage() { - return this.presenceStatusMessage; - } - - public String getResource() { - return jid.getResourcepart(); - } - - public boolean setResource(final String resource) { - final String oldResource = jid.getResourcepart(); - if (oldResource == null || !oldResource.equals(resource)) { - try { - jid = Jid.fromParts(jid.getLocalpart(), jid.getDomainpart(), resource); - return true; - } catch (final InvalidJidException ignored) { - return true; - } - } - return false; - } - - public Jid getJid() { - return jid; - } - - public JSONObject getKeys() { - return keys; - } - - public String getKey(final String name) { - synchronized (this.keys) { - return this.keys.optString(name, null); - } - } - - public int getKeyAsInt(final String name, int defaultValue) { - String key = getKey(name); - try { - return key == null ? defaultValue : Integer.parseInt(key); - } catch (NumberFormatException e) { - return defaultValue; - } - } - - public boolean setKey(final String keyName, final String keyValue) { - synchronized (this.keys) { - try { - this.keys.put(keyName, keyValue); - return true; - } catch (final JSONException e) { - return false; - } - } - } - - public boolean setPrivateKeyAlias(String alias) { - return setKey("private_key_alias", alias); - } - - public String getPrivateKeyAlias() { - return getKey("private_key_alias"); - } - - @Override - public ContentValues getContentValues() { - final ContentValues values = new ContentValues(); - values.put(UUID, uuid); - values.put(USERNAME, jid.getLocalpart()); - values.put(SERVER, jid.getDomainpart()); - values.put(PASSWORD, password); - values.put(OPTIONS, options); - synchronized (this.keys) { - values.put(KEYS, this.keys.toString()); - } - values.put(ROSTERVERSION, rosterVersion); - values.put(AVATAR, avatar); - values.put(DISPLAY_NAME, displayName); - values.put(HOSTNAME, hostname); - values.put(PORT, port); - values.put(STATUS, presenceStatus.toShowString()); - values.put(STATUS_MESSAGE, presenceStatusMessage); - return values; - } - - public AxolotlService getAxolotlService() { - return axolotlService; - } - - public void initAccountServices(final XmppConnectionService context) { - this.mOtrService = new OtrService(context, this); - this.axolotlService = new AxolotlService(this, context); - this.pgpDecryptionService = new PgpDecryptionService(context); - if (xmppConnection != null) { - xmppConnection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService); - } - } - - public OtrService getOtrService() { - return this.mOtrService; - } - - public PgpDecryptionService getPgpDecryptionService() { - return this.pgpDecryptionService; - } - - public XmppConnection getXmppConnection() { - return this.xmppConnection; - } - - public void setXmppConnection(final XmppConnection connection) { - this.xmppConnection = connection; - } - - public String getOtrFingerprint() { - if (this.otrFingerprint == null) { - try { - if (this.mOtrService == null) { - return null; - } - final PublicKey publicKey = this.mOtrService.getPublicKey(); - if (publicKey == null || !(publicKey instanceof DSAPublicKey)) { - return null; - } - this.otrFingerprint = new OtrCryptoEngineImpl().getFingerprint(publicKey); - return this.otrFingerprint; - } catch (final OtrCryptoException ignored) { - return null; - } - } else { - return this.otrFingerprint; - } - } - - public String getRosterVersion() { - if (this.rosterVersion == null) { - return ""; - } else { - return this.rosterVersion; - } - } - - public void setRosterVersion(final String version) { - this.rosterVersion = version; - } - - public int countPresences() { - return this.getSelfContact().getPresences().size(); - } - - public String getPgpSignature() { - return getKey(KEY_PGP_SIGNATURE); - } - - public boolean setPgpSignature(String signature) { - return setKey(KEY_PGP_SIGNATURE, signature); - } - - public boolean unsetPgpSignature() { - synchronized (this.keys) { - return keys.remove(KEY_PGP_SIGNATURE) != null; - } - } - - public long getPgpId() { - synchronized (this.keys) { - if (keys.has(KEY_PGP_ID)) { - try { - return keys.getLong(KEY_PGP_ID); - } catch (JSONException e) { - return 0; - } - } else { - return 0; - } - } - } - - public boolean setPgpSignId(long pgpID) { - synchronized (this.keys) { - try { - keys.put(KEY_PGP_ID, pgpID); - } catch (JSONException e) { - return false; - } - return true; - } - } - - public Roster getRoster() { - return this.roster; - } - - public List getBookmarks() { - return this.bookmarks; - } - - public void setBookmarks(final List bookmarks) { - this.bookmarks = bookmarks; - } - - public boolean hasBookmarkFor(final Jid conferenceJid) { - for (final Bookmark bookmark : this.bookmarks) { - final Jid jid = bookmark.getJid(); - if (jid != null && jid.equals(conferenceJid.toBareJid())) { - return true; - } - } - return false; - } - - public boolean setAvatar(final String filename) { - if (this.avatar != null && this.avatar.equals(filename)) { - return false; - } else { - this.avatar = filename; - return true; - } - } - - public String getAvatar() { - return this.avatar; - } - - public void activateGracePeriod(long duration) { - this.mEndGracePeriod = SystemClock.elapsedRealtime() + duration; - } - - public void deactivateGracePeriod() { - this.mEndGracePeriod = 0L; - } - - public boolean inGracePeriod() { - return SystemClock.elapsedRealtime() < this.mEndGracePeriod; - } - - public String getShareableUri() { - List fingerprints = this.getFingerprints(); - String uri = "xmpp:"+this.getJid().toBareJid().toString(); - if (fingerprints.size() > 0) { - StringBuilder builder = new StringBuilder(uri); - builder.append('?'); - for(int i = 0; i < fingerprints.size(); ++i) { - XmppUri.FingerprintType type = fingerprints.get(i).type; - if (type == XmppUri.FingerprintType.OMEMO) { - builder.append(XmppUri.OMEMO_URI_PARAM); - builder.append(fingerprints.get(i).deviceId); - } else if (type == XmppUri.FingerprintType.OTR) { - builder.append(XmppUri.OTR_URI_PARAM); - } - builder.append('='); - builder.append(fingerprints.get(i).fingerprint); - if (i != fingerprints.size() -1) { - builder.append(';'); - } - } - return builder.toString(); - } else { - return uri; - } - } - - private List getFingerprints() { - ArrayList fingerprints = new ArrayList<>(); - final String otr = this.getOtrFingerprint(); - if (otr != null) { - fingerprints.add(new XmppUri.Fingerprint(XmppUri.FingerprintType.OTR,otr)); - } - fingerprints.add(new XmppUri.Fingerprint(XmppUri.FingerprintType.OMEMO,axolotlService.getOwnFingerprint().substring(2),axolotlService.getOwnDeviceId())); - for(XmppAxolotlSession session : axolotlService.findOwnSessions()) { - if (session.getTrust().isVerified() && session.getTrust().isActive()) { - fingerprints.add(new XmppUri.Fingerprint(XmppUri.FingerprintType.OMEMO,session.getFingerprint().substring(2).replaceAll("\\s",""),session.getRemoteAddress().getDeviceId())); - } - } - return fingerprints; - } - - public boolean isBlocked(final ListItem contact) { - final Jid jid = contact.getJid(); - return jid != null && (blocklist.contains(jid.toBareJid()) || blocklist.contains(jid.toDomainJid())); - } - - public boolean isBlocked(final Jid jid) { - return jid != null && blocklist.contains(jid.toBareJid()); - } - - public Collection getBlocklist() { - return this.blocklist; - } - - public void clearBlocklist() { - getBlocklist().clear(); - } - - public boolean isOnlineAndConnected() { - return this.getStatus() == State.ONLINE && this.getXmppConnection() != null; - } + public static final String TABLENAME = "accounts"; + + public static final String USERNAME = "username"; + public static final String SERVER = "server"; + public static final String PASSWORD = "password"; + public static final String OPTIONS = "options"; + public static final String ROSTERVERSION = "rosterversion"; + public static final String KEYS = "keys"; + public static final String AVATAR = "avatar"; + public static final String DISPLAY_NAME = "display_name"; + public static final String HOSTNAME = "hostname"; + public static final String PORT = "port"; + public static final String STATUS = "status"; + public static final String STATUS_MESSAGE = "status_message"; + + public static final String PINNED_MECHANISM_KEY = "pinned_mechanism"; + + public static final int OPTION_USETLS = 0; + public static final int OPTION_DISABLED = 1; + public static final int OPTION_REGISTER = 2; + public static final int OPTION_USECOMPRESSION = 3; + public static final int OPTION_MAGIC_CREATE = 4; + public final HashSet> inProgressDiscoFetches = new HashSet<>(); + + public boolean httpUploadAvailable(long filesize) { + return xmppConnection != null && xmppConnection.getFeatures().httpUpload(filesize); + } + + public boolean httpUploadAvailable() { + return httpUploadAvailable(0); + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String getDisplayName() { + return displayName; + } + + public XmppConnection.Identity getServerIdentity() { + if (xmppConnection == null) { + return XmppConnection.Identity.UNKNOWN; + } else { + return xmppConnection.getServerIdentity(); + } + } + + public Contact getSelfContact() { + return getRoster().getContact(jid); + } + + public boolean hasPendingPgpIntent(Conversation conversation) { + return pgpDecryptionService != null && pgpDecryptionService.hasPendingIntent(conversation); + } + + public boolean isPgpDecryptionServiceConnected() { + return pgpDecryptionService != null && pgpDecryptionService.isConnected(); + } + + public boolean setShowErrorNotification(boolean newValue) { + boolean oldValue = showErrorNotification(); + setKey("show_error", Boolean.toString(newValue)); + return newValue != oldValue; + } + + public boolean showErrorNotification() { + String key = getKey("show_error"); + return key == null || Boolean.parseBoolean(key); + } + + public enum State { + DISABLED, + OFFLINE, + CONNECTING, + ONLINE, + NO_INTERNET, + UNAUTHORIZED(true), + SERVER_NOT_FOUND(true), + REGISTRATION_FAILED(true), + REGISTRATION_CONFLICT(true), + REGISTRATION_SUCCESSFUL, + REGISTRATION_NOT_SUPPORTED(true), + SECURITY_ERROR(true), + INCOMPATIBLE_SERVER(true), + TOR_NOT_AVAILABLE(true), + BIND_FAILURE(true), + HOST_UNKNOWN(true), + REGISTRATION_PLEASE_WAIT(true), + STREAM_ERROR(true), + POLICY_VIOLATION(true), + REGISTRATION_PASSWORD_TOO_WEAK(true), + PAYMENT_REQUIRED(true), + MISSING_INTERNET_PERMISSION(true); + + private final boolean isError; + + public boolean isError() { + return this.isError; + } + + State(final boolean isError) { + this.isError = isError; + } + + State() { + this(false); + } + + public int getReadableId() { + switch (this) { + case DISABLED: + return R.string.account_status_disabled; + case ONLINE: + return R.string.account_status_online; + case CONNECTING: + return R.string.account_status_connecting; + case OFFLINE: + return R.string.account_status_offline; + case UNAUTHORIZED: + return R.string.account_status_unauthorized; + case SERVER_NOT_FOUND: + return R.string.account_status_not_found; + case NO_INTERNET: + return R.string.account_status_no_internet; + case REGISTRATION_FAILED: + return R.string.account_status_regis_fail; + case REGISTRATION_CONFLICT: + return R.string.account_status_regis_conflict; + case REGISTRATION_SUCCESSFUL: + return R.string.account_status_regis_success; + case REGISTRATION_NOT_SUPPORTED: + return R.string.account_status_regis_not_sup; + case SECURITY_ERROR: + return R.string.account_status_security_error; + case INCOMPATIBLE_SERVER: + return R.string.account_status_incompatible_server; + case TOR_NOT_AVAILABLE: + return R.string.account_status_tor_unavailable; + case BIND_FAILURE: + return R.string.account_status_bind_failure; + case HOST_UNKNOWN: + return R.string.account_status_host_unknown; + case POLICY_VIOLATION: + return R.string.account_status_policy_violation; + case REGISTRATION_PLEASE_WAIT: + return R.string.registration_please_wait; + case REGISTRATION_PASSWORD_TOO_WEAK: + return R.string.registration_password_too_weak; + case STREAM_ERROR: + return R.string.account_status_stream_error; + case PAYMENT_REQUIRED: + return R.string.payment_required; + case MISSING_INTERNET_PERMISSION: + return R.string.missing_internet_permission; + default: + return R.string.account_status_unknown; + } + } + } + + public List pendingConferenceJoins = new CopyOnWriteArrayList<>(); + public List pendingConferenceLeaves = new CopyOnWriteArrayList<>(); + + private static final String KEY_PGP_SIGNATURE = "pgp_signature"; + private static final String KEY_PGP_ID = "pgp_id"; + + protected Jid jid; + protected String password; + protected int options = 0; + protected String rosterVersion; + protected State status = State.OFFLINE; + protected final JSONObject keys; + protected String avatar; + protected String displayName = null; + protected String hostname = null; + protected int port = 5222; + protected boolean online = false; + private OtrService mOtrService = null; + private AxolotlService axolotlService = null; + private PgpDecryptionService pgpDecryptionService = null; + private XmppConnection xmppConnection = null; + private long mEndGracePeriod = 0L; + private String otrFingerprint; + private final Roster roster = new Roster(this); + private List bookmarks = new CopyOnWriteArrayList<>(); + private final Collection blocklist = new CopyOnWriteArraySet<>(); + private Presence.Status presenceStatus = Presence.Status.ONLINE; + private String presenceStatusMessage = null; + + public Account(final Jid jid, final String password) { + this(java.util.UUID.randomUUID().toString(), jid, + password, 0, null, "", null, null, null, 5222, Presence.Status.ONLINE, null); + } + + private Account(final String uuid, final Jid jid, + final String password, final int options, final String rosterVersion, final String keys, + final String avatar, String displayName, String hostname, int port, + final Presence.Status status, String statusMessage) { + this.uuid = uuid; + this.jid = jid; + if (jid.isBareJid()) { + this.setResource("mobile"); + } + this.password = password; + this.options = options; + this.rosterVersion = rosterVersion; + JSONObject tmp; + try { + tmp = new JSONObject(keys); + } catch (JSONException e) { + tmp = new JSONObject(); + } + this.keys = tmp; + this.avatar = avatar; + this.displayName = displayName; + this.hostname = hostname; + this.port = port; + this.presenceStatus = status; + this.presenceStatusMessage = statusMessage; + } + + public static Account fromCursor(final Cursor cursor) { + Jid jid = null; + try { + jid = Jid.fromParts(cursor.getString(cursor.getColumnIndex(USERNAME)), + cursor.getString(cursor.getColumnIndex(SERVER)), "mobile"); + } catch (final InvalidJidException ignored) { + } + return new Account(cursor.getString(cursor.getColumnIndex(UUID)), + jid, + cursor.getString(cursor.getColumnIndex(PASSWORD)), + cursor.getInt(cursor.getColumnIndex(OPTIONS)), + cursor.getString(cursor.getColumnIndex(ROSTERVERSION)), + cursor.getString(cursor.getColumnIndex(KEYS)), + cursor.getString(cursor.getColumnIndex(AVATAR)), + cursor.getString(cursor.getColumnIndex(DISPLAY_NAME)), + cursor.getString(cursor.getColumnIndex(HOSTNAME)), + cursor.getInt(cursor.getColumnIndex(PORT)), + Presence.Status.fromShowString(cursor.getString(cursor.getColumnIndex(STATUS))), + cursor.getString(cursor.getColumnIndex(STATUS_MESSAGE))); + } + + public boolean isOptionSet(final int option) { + return ((options & (1 << option)) != 0); + } + + public void setOption(final int option, final boolean value) { + if (value) { + this.options |= 1 << option; + } else { + this.options &= ~(1 << option); + } + } + + public String getUsername() { + return jid.getLocalpart(); + } + + public boolean setJid(final Jid next) { + final Jid prev = this.jid != null ? this.jid.toBareJid() : null; + this.jid = next; + return prev == null || (next != null && !prev.equals(next.toBareJid()) + ); + } + + public Jid getServer() { + return jid.toDomainJid(); + } + + public String getPassword() { + return password; + } + + public void setPassword(final String password) { + this.password = password; + } + + public void setHostname(String hostname) { + this.hostname = hostname; + } + + public String getHostname() { + return this.hostname == null ? "" : this.hostname; + } + + public boolean isOnion() { + final Jid server = getServer(); + return server != null && server.toString().toLowerCase().endsWith(".onion"); + } + + public void setPort(int port) { + this.port = port; + } + + public int getPort() { + return this.port; + } + + public State getStatus() { + if (isOptionSet(OPTION_DISABLED)) { + return State.DISABLED; + } else { + return this.status; + } + } + + public void setStatus(final State status) { + this.status = status; + } + + public boolean errorStatus() { + return getStatus().isError(); + } + + public boolean hasErrorStatus() { + return getXmppConnection() != null + && (getStatus().isError() || getStatus() == State.CONNECTING) + && getXmppConnection().getAttempt() >= 3; + } + + public void setPresenceStatus(Presence.Status status) { + this.presenceStatus = status; + } + + public Presence.Status getPresenceStatus() { + return this.presenceStatus; + } + + public void setPresenceStatusMessage(String message) { + this.presenceStatusMessage = message; + } + + public String getPresenceStatusMessage() { + return this.presenceStatusMessage; + } + + public String getResource() { + return jid.getResourcepart(); + } + + public boolean setResource(final String resource) { + final String oldResource = jid.getResourcepart(); + if (oldResource == null || !oldResource.equals(resource)) { + try { + jid = Jid.fromParts(jid.getLocalpart(), jid.getDomainpart(), resource); + return true; + } catch (final InvalidJidException ignored) { + return true; + } + } + return false; + } + + public Jid getJid() { + return jid; + } + + public JSONObject getKeys() { + return keys; + } + + public String getKey(final String name) { + synchronized (this.keys) { + return this.keys.optString(name, null); + } + } + + public int getKeyAsInt(final String name, int defaultValue) { + String key = getKey(name); + try { + return key == null ? defaultValue : Integer.parseInt(key); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + public boolean setKey(final String keyName, final String keyValue) { + synchronized (this.keys) { + try { + this.keys.put(keyName, keyValue); + return true; + } catch (final JSONException e) { + return false; + } + } + } + + public boolean setPrivateKeyAlias(String alias) { + return setKey("private_key_alias", alias); + } + + public String getPrivateKeyAlias() { + return getKey("private_key_alias"); + } + + @Override + public ContentValues getContentValues() { + final ContentValues values = new ContentValues(); + values.put(UUID, uuid); + values.put(USERNAME, jid.getLocalpart()); + values.put(SERVER, jid.getDomainpart()); + values.put(PASSWORD, password); + values.put(OPTIONS, options); + synchronized (this.keys) { + values.put(KEYS, this.keys.toString()); + } + values.put(ROSTERVERSION, rosterVersion); + values.put(AVATAR, avatar); + values.put(DISPLAY_NAME, displayName); + values.put(HOSTNAME, hostname); + values.put(PORT, port); + values.put(STATUS, presenceStatus.toShowString()); + values.put(STATUS_MESSAGE, presenceStatusMessage); + return values; + } + + public AxolotlService getAxolotlService() { + return axolotlService; + } + + public void initAccountServices(final XmppConnectionService context) { + this.mOtrService = new OtrService(context, this); + this.axolotlService = new AxolotlService(this, context); + this.pgpDecryptionService = new PgpDecryptionService(context); + if (xmppConnection != null) { + xmppConnection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService); + } + } + + public OtrService getOtrService() { + return this.mOtrService; + } + + public PgpDecryptionService getPgpDecryptionService() { + return this.pgpDecryptionService; + } + + public XmppConnection getXmppConnection() { + return this.xmppConnection; + } + + public void setXmppConnection(final XmppConnection connection) { + this.xmppConnection = connection; + } + + public String getOtrFingerprint() { + if (this.otrFingerprint == null) { + try { + if (this.mOtrService == null) { + return null; + } + final PublicKey publicKey = this.mOtrService.getPublicKey(); + if (publicKey == null || !(publicKey instanceof DSAPublicKey)) { + return null; + } + this.otrFingerprint = new OtrCryptoEngineImpl().getFingerprint(publicKey); + return this.otrFingerprint; + } catch (final OtrCryptoException ignored) { + return null; + } + } else { + return this.otrFingerprint; + } + } + + public String getRosterVersion() { + if (this.rosterVersion == null) { + return ""; + } else { + return this.rosterVersion; + } + } + + public void setRosterVersion(final String version) { + this.rosterVersion = version; + } + + public int countPresences() { + return this.getSelfContact().getPresences().size(); + } + + public String getPgpSignature() { + return getKey(KEY_PGP_SIGNATURE); + } + + public boolean setPgpSignature(String signature) { + return setKey(KEY_PGP_SIGNATURE, signature); + } + + public boolean unsetPgpSignature() { + synchronized (this.keys) { + return keys.remove(KEY_PGP_SIGNATURE) != null; + } + } + + public long getPgpId() { + synchronized (this.keys) { + if (keys.has(KEY_PGP_ID)) { + try { + return keys.getLong(KEY_PGP_ID); + } catch (JSONException e) { + return 0; + } + } else { + return 0; + } + } + } + + public boolean setPgpSignId(long pgpID) { + synchronized (this.keys) { + try { + keys.put(KEY_PGP_ID, pgpID); + } catch (JSONException e) { + return false; + } + return true; + } + } + + public Roster getRoster() { + return this.roster; + } + + public List getBookmarks() { + return this.bookmarks; + } + + public void setBookmarks(final List bookmarks) { + this.bookmarks = bookmarks; + } + + public boolean hasBookmarkFor(final Jid conferenceJid) { + for (final Bookmark bookmark : this.bookmarks) { + final Jid jid = bookmark.getJid(); + if (jid != null && jid.equals(conferenceJid.toBareJid())) { + return true; + } + } + return false; + } + + public boolean setAvatar(final String filename) { + if (this.avatar != null && this.avatar.equals(filename)) { + return false; + } else { + this.avatar = filename; + return true; + } + } + + public String getAvatar() { + return this.avatar; + } + + public void activateGracePeriod(long duration) { + this.mEndGracePeriod = SystemClock.elapsedRealtime() + duration; + } + + public void deactivateGracePeriod() { + this.mEndGracePeriod = 0L; + } + + public boolean inGracePeriod() { + return SystemClock.elapsedRealtime() < this.mEndGracePeriod; + } + + public String getShareableUri() { + List fingerprints = this.getFingerprints(); + String uri = "xmpp:" + this.getJid().toBareJid().toString(); + if (fingerprints.size() > 0) { + StringBuilder builder = new StringBuilder(uri); + builder.append('?'); + for (int i = 0; i < fingerprints.size(); ++i) { + XmppUri.FingerprintType type = fingerprints.get(i).type; + if (type == XmppUri.FingerprintType.OMEMO) { + builder.append(XmppUri.OMEMO_URI_PARAM); + builder.append(fingerprints.get(i).deviceId); + } else if (type == XmppUri.FingerprintType.OTR) { + builder.append(XmppUri.OTR_URI_PARAM); + } + builder.append('='); + builder.append(fingerprints.get(i).fingerprint); + if (i != fingerprints.size() - 1) { + builder.append(';'); + } + } + return builder.toString(); + } else { + return uri; + } + } + + private List getFingerprints() { + ArrayList fingerprints = new ArrayList<>(); + final String otr = this.getOtrFingerprint(); + if (otr != null) { + fingerprints.add(new XmppUri.Fingerprint(XmppUri.FingerprintType.OTR, otr)); + } + fingerprints.add(new XmppUri.Fingerprint(XmppUri.FingerprintType.OMEMO, axolotlService.getOwnFingerprint().substring(2), axolotlService.getOwnDeviceId())); + for (XmppAxolotlSession session : axolotlService.findOwnSessions()) { + if (session.getTrust().isVerified() && session.getTrust().isActive()) { + fingerprints.add(new XmppUri.Fingerprint(XmppUri.FingerprintType.OMEMO, session.getFingerprint().substring(2).replaceAll("\\s", ""), session.getRemoteAddress().getDeviceId())); + } + } + return fingerprints; + } + + public boolean isBlocked(final ListItem contact) { + final Jid jid = contact.getJid(); + return jid != null && (blocklist.contains(jid.toBareJid()) || blocklist.contains(jid.toDomainJid())); + } + + public boolean isBlocked(final Jid jid) { + return jid != null && blocklist.contains(jid.toBareJid()); + } + + public Collection getBlocklist() { + return this.blocklist; + } + + public void clearBlocklist() { + getBlocklist().clear(); + } + + public boolean isOnlineAndConnected() { + return this.getStatus() == State.ONLINE && this.getXmppConnection() != null; + } } diff --git a/src/main/java/de/pixart/messenger/entities/Blockable.java b/src/main/java/de/pixart/messenger/entities/Blockable.java index 07860bcfe..9357f1cdb 100644 --- a/src/main/java/de/pixart/messenger/entities/Blockable.java +++ b/src/main/java/de/pixart/messenger/entities/Blockable.java @@ -3,9 +3,13 @@ package de.pixart.messenger.entities; import de.pixart.messenger.xmpp.jid.Jid; public interface Blockable { - public boolean isBlocked(); - public boolean isDomainBlocked(); - public Jid getBlockedJid(); - public Jid getJid(); - public Account getAccount(); + public boolean isBlocked(); + + public boolean isDomainBlocked(); + + public Jid getBlockedJid(); + + public Jid getJid(); + + public Account getAccount(); } diff --git a/src/main/java/de/pixart/messenger/entities/Bookmark.java b/src/main/java/de/pixart/messenger/entities/Bookmark.java index 9c99a2baf..c2e31039b 100644 --- a/src/main/java/de/pixart/messenger/entities/Bookmark.java +++ b/src/main/java/de/pixart/messenger/entities/Bookmark.java @@ -12,160 +12,160 @@ import de.pixart.messenger.xmpp.jid.Jid; public class Bookmark extends Element implements ListItem { - private Account account; - private Conversation mJoinedConversation; - - public Bookmark(final Account account, final Jid jid) { - super("conference"); - this.setAttribute("jid", jid.toString()); - this.account = account; - } - - private Bookmark(Account account) { - super("conference"); - this.account = account; - } - - public static Bookmark parse(Element element, Account account) { - Bookmark bookmark = new Bookmark(account); - bookmark.setAttributes(element.getAttributes()); - bookmark.setChildren(element.getChildren()); - return bookmark; - } - - public void setAutojoin(boolean autojoin) { - if (autojoin) { - this.setAttribute("autojoin", "true"); - } else { - this.setAttribute("autojoin", "false"); - } - } - - @Override - public int compareTo(final ListItem another) { - return this.getDisplayName().compareToIgnoreCase( - another.getDisplayName()); - } - - @Override - public String getDisplayName() { - if (this.mJoinedConversation != null) { - return this.mJoinedConversation.getName(); - } else if (getBookmarkName() != null - && !getBookmarkName().trim().isEmpty()) { - return getBookmarkName().trim(); - } else { - Jid jid = this.getJid(); - String name = jid != null ? jid.getLocalpart() : getAttribute("jid"); - return name != null ? name : ""; - } - } - - @Override - public String getDisplayJid() { - Jid jid = getJid(); - if (jid != null) { - return jid.toString(); - } else { - return getAttribute("jid"); //fallback if jid wasn't parsable - } - } - - @Override - public Jid getJid() { - return this.getAttributeAsJid("jid"); - } - - @Override - public List getTags(Context context) { - ArrayList tags = new ArrayList<>(); - for (Element element : getChildren()) { - if (element.getName().equals("group") && element.getContent() != null) { - String group = element.getContent(); - tags.add(new Tag(group, UIHelper.getColorForName(group))); - } - } - return tags; - } - - public String getNick() { - return this.findChildContent("nick"); - } - - public void setNick(String nick) { - Element element = this.findChild("nick"); - if (element == null) { - element = this.addChild("nick"); - } - element.setContent(nick); - } - - public boolean autojoin() { - return this.getAttributeAsBoolean("autojoin"); - } - - public String getPassword() { - return this.findChildContent("password"); - } - - public void setPassword(String password) { - Element element = this.findChild("password"); - if (element != null) { - element.setContent(password); - } - } - - @Override - public boolean match(Context context, String needle) { - if (needle == null) { - return true; - } - needle = needle.toLowerCase(Locale.US); - final Jid jid = getJid(); - return (jid != null && jid.toString().contains(needle)) || - getDisplayName().toLowerCase(Locale.US).contains(needle) || - matchInTag(context, needle); - } - - private boolean matchInTag(Context context, String needle) { - needle = needle.toLowerCase(Locale.US); - for (Tag tag : getTags(context)) { - if (tag.getName().toLowerCase(Locale.US).contains(needle)) { - return true; - } - } - return false; - } - - public Account getAccount() { - return this.account; - } - - public Conversation getConversation() { - return this.mJoinedConversation; - } - - public void setConversation(Conversation conversation) { - this.mJoinedConversation = conversation; - } - - public String getBookmarkName() { - return this.getAttribute("name"); - } - - public boolean setBookmarkName(String name) { - String before = getBookmarkName(); - if (name != null && !name.equals(before)) { - this.setAttribute("name", name); - return true; - } else { - return false; - } - } - - public void unregisterConversation() { - if (this.mJoinedConversation != null) { - this.mJoinedConversation.deregisterWithBookmark(); - } - } + private Account account; + private Conversation mJoinedConversation; + + public Bookmark(final Account account, final Jid jid) { + super("conference"); + this.setAttribute("jid", jid.toString()); + this.account = account; + } + + private Bookmark(Account account) { + super("conference"); + this.account = account; + } + + public static Bookmark parse(Element element, Account account) { + Bookmark bookmark = new Bookmark(account); + bookmark.setAttributes(element.getAttributes()); + bookmark.setChildren(element.getChildren()); + return bookmark; + } + + public void setAutojoin(boolean autojoin) { + if (autojoin) { + this.setAttribute("autojoin", "true"); + } else { + this.setAttribute("autojoin", "false"); + } + } + + @Override + public int compareTo(final ListItem another) { + return this.getDisplayName().compareToIgnoreCase( + another.getDisplayName()); + } + + @Override + public String getDisplayName() { + if (this.mJoinedConversation != null) { + return this.mJoinedConversation.getName(); + } else if (getBookmarkName() != null + && !getBookmarkName().trim().isEmpty()) { + return getBookmarkName().trim(); + } else { + Jid jid = this.getJid(); + String name = jid != null ? jid.getLocalpart() : getAttribute("jid"); + return name != null ? name : ""; + } + } + + @Override + public String getDisplayJid() { + Jid jid = getJid(); + if (jid != null) { + return jid.toString(); + } else { + return getAttribute("jid"); //fallback if jid wasn't parsable + } + } + + @Override + public Jid getJid() { + return this.getAttributeAsJid("jid"); + } + + @Override + public List getTags(Context context) { + ArrayList tags = new ArrayList<>(); + for (Element element : getChildren()) { + if (element.getName().equals("group") && element.getContent() != null) { + String group = element.getContent(); + tags.add(new Tag(group, UIHelper.getColorForName(group))); + } + } + return tags; + } + + public String getNick() { + return this.findChildContent("nick"); + } + + public void setNick(String nick) { + Element element = this.findChild("nick"); + if (element == null) { + element = this.addChild("nick"); + } + element.setContent(nick); + } + + public boolean autojoin() { + return this.getAttributeAsBoolean("autojoin"); + } + + public String getPassword() { + return this.findChildContent("password"); + } + + public void setPassword(String password) { + Element element = this.findChild("password"); + if (element != null) { + element.setContent(password); + } + } + + @Override + public boolean match(Context context, String needle) { + if (needle == null) { + return true; + } + needle = needle.toLowerCase(Locale.US); + final Jid jid = getJid(); + return (jid != null && jid.toString().contains(needle)) || + getDisplayName().toLowerCase(Locale.US).contains(needle) || + matchInTag(context, needle); + } + + private boolean matchInTag(Context context, String needle) { + needle = needle.toLowerCase(Locale.US); + for (Tag tag : getTags(context)) { + if (tag.getName().toLowerCase(Locale.US).contains(needle)) { + return true; + } + } + return false; + } + + public Account getAccount() { + return this.account; + } + + public Conversation getConversation() { + return this.mJoinedConversation; + } + + public void setConversation(Conversation conversation) { + this.mJoinedConversation = conversation; + } + + public String getBookmarkName() { + return this.getAttribute("name"); + } + + public boolean setBookmarkName(String name) { + String before = getBookmarkName(); + if (name != null && !name.equals(before)) { + this.setAttribute("name", name); + return true; + } else { + return false; + } + } + + public void unregisterConversation() { + if (this.mJoinedConversation != null) { + this.mJoinedConversation.deregisterWithBookmark(); + } + } } diff --git a/src/main/java/de/pixart/messenger/entities/Contact.java b/src/main/java/de/pixart/messenger/entities/Contact.java index 32af78de0..06f7ea4d0 100644 --- a/src/main/java/de/pixart/messenger/entities/Contact.java +++ b/src/main/java/de/pixart/messenger/entities/Contact.java @@ -22,534 +22,535 @@ import de.pixart.messenger.xmpp.jid.Jid; import de.pixart.messenger.xmpp.pep.Avatar; public class Contact implements ListItem, Blockable { - public static final String TABLENAME = "contacts"; - - public static final String SYSTEMNAME = "systemname"; - public static final String SERVERNAME = "servername"; - public static final String JID = "jid"; - public static final String OPTIONS = "options"; - public static final String SYSTEMACCOUNT = "systemaccount"; - public static final String PHOTOURI = "photouri"; - public static final String KEYS = "pgpkey"; - public static final String ACCOUNT = "accountUuid"; - public static final String AVATAR = "avatar"; - public static final String LAST_PRESENCE = "last_presence"; - public static final String LAST_TIME = "last_time"; - public static final String GROUPS = "groups"; - protected String accountUuid; - protected String systemName; - protected String serverName; - protected String presenceName; - protected String commonName; - protected Jid jid; - protected int subscription = 0; - protected String systemAccount; - protected String photoUri; - protected JSONObject keys = new JSONObject(); - protected JSONArray groups = new JSONArray(); - protected final Presences presences = new Presences(); - protected Account account; - protected Avatar avatar; - - private boolean mActive = false; - private long mLastseen = 0; - private String mLastPresence = null; - - public Contact(final String account, final String systemName, final String serverName, - final Jid jid, final int subscription, final String photoUri, - final String systemAccount, final String keys, final String avatar, final long lastseen, - final String presence, final String groups) { - this.accountUuid = account; - this.systemName = systemName; - this.serverName = serverName; - this.jid = jid; - this.subscription = subscription; - this.photoUri = photoUri; - this.systemAccount = systemAccount; - try { - this.keys = (keys == null ? new JSONObject("") : new JSONObject(keys)); - } catch (JSONException e) { - this.keys = new JSONObject(); - } - if (avatar != null) { - this.avatar = new Avatar(); - this.avatar.sha1sum = avatar; - this.avatar.origin = Avatar.Origin.VCARD; //always assume worst - } - try { - this.groups = (groups == null ? new JSONArray() : new JSONArray(groups)); - } catch (JSONException e) { - this.groups = new JSONArray(); - } - this.mLastseen = lastseen; - this.mLastPresence = presence; - } - - public Contact(final Jid jid) { - this.jid = jid; - } - - public static Contact fromCursor(final Cursor cursor) { - final Jid jid; - try { - jid = Jid.fromString(cursor.getString(cursor.getColumnIndex(JID)), true); - } catch (final InvalidJidException e) { - // TODO: Borked DB... handle this somehow? - return null; - } - return new Contact(cursor.getString(cursor.getColumnIndex(ACCOUNT)), - cursor.getString(cursor.getColumnIndex(SYSTEMNAME)), - cursor.getString(cursor.getColumnIndex(SERVERNAME)), - jid, - cursor.getInt(cursor.getColumnIndex(OPTIONS)), - cursor.getString(cursor.getColumnIndex(PHOTOURI)), - cursor.getString(cursor.getColumnIndex(SYSTEMACCOUNT)), - cursor.getString(cursor.getColumnIndex(KEYS)), - cursor.getString(cursor.getColumnIndex(AVATAR)), - cursor.getLong(cursor.getColumnIndex(LAST_TIME)), - cursor.getString(cursor.getColumnIndex(LAST_PRESENCE)), - cursor.getString(cursor.getColumnIndex(GROUPS))); - } - - public String getDisplayName() { - if (this.commonName != null && Config.X509_VERIFICATION) { - return this.commonName; - } else if (this.systemName != null) { - return this.systemName; - } else if (this.serverName != null) { - return this.serverName; + public static final String TABLENAME = "contacts"; + + public static final String SYSTEMNAME = "systemname"; + public static final String SERVERNAME = "servername"; + public static final String JID = "jid"; + public static final String OPTIONS = "options"; + public static final String SYSTEMACCOUNT = "systemaccount"; + public static final String PHOTOURI = "photouri"; + public static final String KEYS = "pgpkey"; + public static final String ACCOUNT = "accountUuid"; + public static final String AVATAR = "avatar"; + public static final String LAST_PRESENCE = "last_presence"; + public static final String LAST_TIME = "last_time"; + public static final String GROUPS = "groups"; + protected String accountUuid; + protected String systemName; + protected String serverName; + protected String presenceName; + protected String commonName; + protected Jid jid; + protected int subscription = 0; + protected String systemAccount; + protected String photoUri; + protected JSONObject keys = new JSONObject(); + protected JSONArray groups = new JSONArray(); + protected final Presences presences = new Presences(); + protected Account account; + protected Avatar avatar; + + private boolean mActive = false; + private long mLastseen = 0; + private String mLastPresence = null; + + public Contact(final String account, final String systemName, final String serverName, + final Jid jid, final int subscription, final String photoUri, + final String systemAccount, final String keys, final String avatar, final long lastseen, + final String presence, final String groups) { + this.accountUuid = account; + this.systemName = systemName; + this.serverName = serverName; + this.jid = jid; + this.subscription = subscription; + this.photoUri = photoUri; + this.systemAccount = systemAccount; + try { + this.keys = (keys == null ? new JSONObject("") : new JSONObject(keys)); + } catch (JSONException e) { + this.keys = new JSONObject(); + } + if (avatar != null) { + this.avatar = new Avatar(); + this.avatar.sha1sum = avatar; + this.avatar.origin = Avatar.Origin.VCARD; //always assume worst + } + try { + this.groups = (groups == null ? new JSONArray() : new JSONArray(groups)); + } catch (JSONException e) { + this.groups = new JSONArray(); + } + this.mLastseen = lastseen; + this.mLastPresence = presence; + } + + public Contact(final Jid jid) { + this.jid = jid; + } + + public static Contact fromCursor(final Cursor cursor) { + final Jid jid; + try { + jid = Jid.fromString(cursor.getString(cursor.getColumnIndex(JID)), true); + } catch (final InvalidJidException e) { + // TODO: Borked DB... handle this somehow? + return null; + } + return new Contact(cursor.getString(cursor.getColumnIndex(ACCOUNT)), + cursor.getString(cursor.getColumnIndex(SYSTEMNAME)), + cursor.getString(cursor.getColumnIndex(SERVERNAME)), + jid, + cursor.getInt(cursor.getColumnIndex(OPTIONS)), + cursor.getString(cursor.getColumnIndex(PHOTOURI)), + cursor.getString(cursor.getColumnIndex(SYSTEMACCOUNT)), + cursor.getString(cursor.getColumnIndex(KEYS)), + cursor.getString(cursor.getColumnIndex(AVATAR)), + cursor.getLong(cursor.getColumnIndex(LAST_TIME)), + cursor.getString(cursor.getColumnIndex(LAST_PRESENCE)), + cursor.getString(cursor.getColumnIndex(GROUPS))); + } + + public String getDisplayName() { + if (this.commonName != null && Config.X509_VERIFICATION) { + return this.commonName; + } else if (this.systemName != null) { + return this.systemName; + } else if (this.serverName != null) { + return this.serverName; } else if (this.presenceName != null && mutualPresenceSubscription()) { - return this.presenceName; - } else if (jid.hasLocalpart()) { - return jid.getLocalpart(); - } else { - return jid.getDomainpart(); - } - } - - @Override - public String getDisplayJid() { - if (jid != null) { - return jid.toString(); - } else { - return null; - } - } - - public String getProfilePhoto() { - return this.photoUri; - } - - public Jid getJid() { - return jid; - } - - @Override - public List getTags(Context context) { - final ArrayList tags = new ArrayList<>(); - for (final String group : getGroups()) { - tags.add(new Tag(group, UIHelper.getColorForName(group))); - } - Presence.Status status = getShownStatus(); - if (status != Presence.Status.OFFLINE) { - tags.add(UIHelper.getTagForStatus(context, status)); - } - if (isBlocked()) { - tags.add(new Tag("blocked", 0xff2e2f3b)); - } - return tags; - } - - public boolean match(Context context, String needle) { - if (needle == null || needle.isEmpty()) { - return true; - } - needle = needle.toLowerCase(Locale.US).trim(); - String[] parts = needle.split("\\s+"); - if (parts.length > 1) { - for(int i = 0; i < parts.length; ++i) { - if (!match(context, parts[i])) { - return false; - } - } - return true; - } else { - return jid.toString().contains(needle) || - getDisplayName().toLowerCase(Locale.US).contains(needle) || - matchInTag(context, needle); - } - } - - private boolean matchInTag(Context context, String needle) { - needle = needle.toLowerCase(Locale.US); - for (Tag tag : getTags(context)) { - if (tag.getName().toLowerCase(Locale.US).contains(needle)) { - return true; - } - } - return false; - } - - public ContentValues getContentValues() { - synchronized (this.keys) { - final ContentValues values = new ContentValues(); - values.put(ACCOUNT, accountUuid); - values.put(SYSTEMNAME, systemName); - values.put(SERVERNAME, serverName); - values.put(JID, jid.toPreppedString()); - values.put(OPTIONS, subscription); - values.put(SYSTEMACCOUNT, systemAccount); - values.put(PHOTOURI, photoUri); - values.put(KEYS, keys.toString()); - values.put(AVATAR, avatar == null ? null : avatar.getFilename()); - values.put(LAST_PRESENCE, mLastPresence); - values.put(LAST_TIME, mLastseen); - values.put(GROUPS, groups.toString()); - return values; - } - } - - public Account getAccount() { - return this.account; - } - - public void setAccount(Account account) { - this.account = account; - this.accountUuid = account.getUuid(); - } - - public Presences getPresences() { - return this.presences; - } - - public void updatePresence(final String resource, final Presence presence) { - this.presences.updatePresence(resource, presence); - } - - public void removePresence(final String resource) { - this.presences.removePresence(resource); - } - - public void clearPresences() { - this.presences.clearPresences(); - this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST); - } - - public Presence.Status getShownStatus() { - return this.presences.getShownStatus(); - } - - public boolean setPhotoUri(String uri) { - if (uri != null && !uri.equals(this.photoUri)) { - this.photoUri = uri; - return true; - } else if (this.photoUri != null && uri == null) { - this.photoUri = null; - return true; - } else { - return false; - } - } - - public void setServerName(String serverName) { - this.serverName = serverName; - } - - public void setSystemName(String systemName) { - this.systemName = systemName; - } - - public void setPresenceName(String presenceName) { - this.presenceName = presenceName; - } - - public Uri getSystemAccount() { - if (systemAccount == null) { - return null; - } else { - String[] parts = systemAccount.split("#"); - if (parts.length != 2) { - return null; - } else { - long id = Long.parseLong(parts[0]); - return ContactsContract.Contacts.getLookupUri(id, parts[1]); - } - } - } - - public void setSystemAccount(String account) { - this.systemAccount = account; - } - - public List getGroups() { - ArrayList groups = new ArrayList(); - for (int i = 0; i < this.groups.length(); ++i) { - try { - groups.add(this.groups.getString(i)); - } catch (final JSONException ignored) { - } - } - return groups; - } - - public ArrayList getOtrFingerprints() { - synchronized (this.keys) { - final ArrayList fingerprints = new ArrayList(); - try { - if (this.keys.has("otr_fingerprints")) { - final JSONArray prints = this.keys.getJSONArray("otr_fingerprints"); - for (int i = 0; i < prints.length(); ++i) { - final String print = prints.isNull(i) ? null : prints.getString(i); - if (print != null && !print.isEmpty()) { - fingerprints.add(prints.getString(i)); - } - } - } - } catch (final JSONException ignored) { - - } - return fingerprints; - } - } - public boolean addOtrFingerprint(String print) { - synchronized (this.keys) { - if (getOtrFingerprints().contains(print)) { - return false; - } - try { - JSONArray fingerprints; - if (!this.keys.has("otr_fingerprints")) { - fingerprints = new JSONArray(); - } else { - fingerprints = this.keys.getJSONArray("otr_fingerprints"); - } - fingerprints.put(print); - this.keys.put("otr_fingerprints", fingerprints); - return true; - } catch (final JSONException ignored) { - return false; - } - } - } - - public long getPgpKeyId() { - synchronized (this.keys) { - if (this.keys.has("pgp_keyid")) { - try { - return this.keys.getLong("pgp_keyid"); - } catch (JSONException e) { - return 0; - } - } else { - return 0; - } - } - } - - public void setPgpKeyId(long keyId) { - synchronized (this.keys) { - try { - this.keys.put("pgp_keyid", keyId); - } catch (final JSONException ignored) { - } - } - } - - public void setOption(int option) { - this.subscription |= 1 << option; - } - - public void resetOption(int option) { - this.subscription &= ~(1 << option); - } - - public boolean getOption(int option) { - return ((this.subscription & (1 << option)) != 0); - } - - public boolean showInRoster() { - return (this.getOption(Contact.Options.IN_ROSTER) && (!this - .getOption(Contact.Options.DIRTY_DELETE))) - || (this.getOption(Contact.Options.DIRTY_PUSH)); - } - - public void parseSubscriptionFromElement(Element item) { - String ask = item.getAttribute("ask"); - String subscription = item.getAttribute("subscription"); - - if (subscription != null) { - switch (subscription) { - case "to": - this.resetOption(Options.FROM); - this.setOption(Options.TO); - break; - case "from": - this.resetOption(Options.TO); - this.setOption(Options.FROM); - this.resetOption(Options.PREEMPTIVE_GRANT); - this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST); - break; - case "both": - this.setOption(Options.TO); - this.setOption(Options.FROM); - this.resetOption(Options.PREEMPTIVE_GRANT); - this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST); - break; - case "none": - this.resetOption(Options.FROM); - this.resetOption(Options.TO); - break; - } - } - - // do NOT override asking if pending push request - if (!this.getOption(Contact.Options.DIRTY_PUSH)) { - if ((ask != null) && (ask.equals("subscribe"))) { - this.setOption(Contact.Options.ASKING); - } else { - this.resetOption(Contact.Options.ASKING); - } - } - } - - public void parseGroupsFromElement(Element item) { - this.groups = new JSONArray(); - for (Element element : item.getChildren()) { - if (element.getName().equals("group") && element.getContent() != null) { - this.groups.put(element.getContent()); - } - } - } - - public Element asElement() { - final Element item = new Element("item"); - item.setAttribute("jid", this.jid.toString()); - if (this.serverName != null) { - item.setAttribute("name", this.serverName); - } - for (String group : getGroups()) { - item.addChild("group").setContent(group); - } - return item; - } - - @Override - public int compareTo(final ListItem another) { - return this.getDisplayName().compareToIgnoreCase( - another.getDisplayName()); - } - - public Jid getServer() { - return getJid().toDomainJid(); - } - - public boolean setAvatar(Avatar avatar) { - if (this.avatar != null && this.avatar.equals(avatar)) { - return false; - } else { - if (this.avatar != null && this.avatar.origin == Avatar.Origin.PEP && avatar.origin == Avatar.Origin.VCARD) { - return false; - } - this.avatar = avatar; - return true; - } - } - - public String getAvatar() { - return avatar == null ? null : avatar.getFilename(); - } - - public boolean deleteOtrFingerprint(String fingerprint) { - synchronized (this.keys) { - boolean success = false; - try { - if (this.keys.has("otr_fingerprints")) { - JSONArray newPrints = new JSONArray(); - JSONArray oldPrints = this.keys - .getJSONArray("otr_fingerprints"); - for (int i = 0; i < oldPrints.length(); ++i) { - if (!oldPrints.getString(i).equals(fingerprint)) { - newPrints.put(oldPrints.getString(i)); - } else { - success = true; - } - } - this.keys.put("otr_fingerprints", newPrints); - } - return success; - } catch (JSONException e) { - return false; - } - } - } - - public boolean mutualPresenceSubscription() { - return getOption(Options.FROM) && getOption(Options.TO); - } - - @Override - public boolean isBlocked() { - return getAccount().isBlocked(this); - } - - @Override - public boolean isDomainBlocked() { - return getAccount().isBlocked(this.getJid().toDomainJid()); - } - - @Override - public Jid getBlockedJid() { - if (isDomainBlocked()) { - return getJid().toDomainJid(); - } else { - return getJid(); - } - } - - public boolean isSelf() { - return account.getJid().toBareJid().equals(getJid().toBareJid()); - } - - public void setCommonName(String cn) { - this.commonName = cn; - } - - public void flagActive() { - this.mActive = true; - } - - public void flagInactive() { - this.mActive = false; - } - - public boolean isActive() { - return this.mActive; - } - - public void setLastseen(long timestamp) { - this.mLastseen = Math.max(timestamp, mLastseen); - } - - public long getLastseen() { - return this.mLastseen; - } - - public void setLastResource(String resource) { - this.mLastPresence = resource; - } - - public String getLastResource() { - return this.mLastPresence; - } - - public final class Options { - public static final int TO = 0; - public static final int FROM = 1; - public static final int ASKING = 2; - public static final int PREEMPTIVE_GRANT = 3; - public static final int IN_ROSTER = 4; - public static final int PENDING_SUBSCRIPTION_REQUEST = 5; - public static final int DIRTY_PUSH = 6; - public static final int DIRTY_DELETE = 7; - } + return this.presenceName; + } else if (jid.hasLocalpart()) { + return jid.getLocalpart(); + } else { + return jid.getDomainpart(); + } + } + + @Override + public String getDisplayJid() { + if (jid != null) { + return jid.toString(); + } else { + return null; + } + } + + public String getProfilePhoto() { + return this.photoUri; + } + + public Jid getJid() { + return jid; + } + + @Override + public List getTags(Context context) { + final ArrayList tags = new ArrayList<>(); + for (final String group : getGroups()) { + tags.add(new Tag(group, UIHelper.getColorForName(group))); + } + Presence.Status status = getShownStatus(); + if (status != Presence.Status.OFFLINE) { + tags.add(UIHelper.getTagForStatus(context, status)); + } + if (isBlocked()) { + tags.add(new Tag("blocked", 0xff2e2f3b)); + } + return tags; + } + + public boolean match(Context context, String needle) { + if (needle == null || needle.isEmpty()) { + return true; + } + needle = needle.toLowerCase(Locale.US).trim(); + String[] parts = needle.split("\\s+"); + if (parts.length > 1) { + for (int i = 0; i < parts.length; ++i) { + if (!match(context, parts[i])) { + return false; + } + } + return true; + } else { + return jid.toString().contains(needle) || + getDisplayName().toLowerCase(Locale.US).contains(needle) || + matchInTag(context, needle); + } + } + + private boolean matchInTag(Context context, String needle) { + needle = needle.toLowerCase(Locale.US); + for (Tag tag : getTags(context)) { + if (tag.getName().toLowerCase(Locale.US).contains(needle)) { + return true; + } + } + return false; + } + + public ContentValues getContentValues() { + synchronized (this.keys) { + final ContentValues values = new ContentValues(); + values.put(ACCOUNT, accountUuid); + values.put(SYSTEMNAME, systemName); + values.put(SERVERNAME, serverName); + values.put(JID, jid.toPreppedString()); + values.put(OPTIONS, subscription); + values.put(SYSTEMACCOUNT, systemAccount); + values.put(PHOTOURI, photoUri); + values.put(KEYS, keys.toString()); + values.put(AVATAR, avatar == null ? null : avatar.getFilename()); + values.put(LAST_PRESENCE, mLastPresence); + values.put(LAST_TIME, mLastseen); + values.put(GROUPS, groups.toString()); + return values; + } + } + + public Account getAccount() { + return this.account; + } + + public void setAccount(Account account) { + this.account = account; + this.accountUuid = account.getUuid(); + } + + public Presences getPresences() { + return this.presences; + } + + public void updatePresence(final String resource, final Presence presence) { + this.presences.updatePresence(resource, presence); + } + + public void removePresence(final String resource) { + this.presences.removePresence(resource); + } + + public void clearPresences() { + this.presences.clearPresences(); + this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST); + } + + public Presence.Status getShownStatus() { + return this.presences.getShownStatus(); + } + + public boolean setPhotoUri(String uri) { + if (uri != null && !uri.equals(this.photoUri)) { + this.photoUri = uri; + return true; + } else if (this.photoUri != null && uri == null) { + this.photoUri = null; + return true; + } else { + return false; + } + } + + public void setServerName(String serverName) { + this.serverName = serverName; + } + + public void setSystemName(String systemName) { + this.systemName = systemName; + } + + public void setPresenceName(String presenceName) { + this.presenceName = presenceName; + } + + public Uri getSystemAccount() { + if (systemAccount == null) { + return null; + } else { + String[] parts = systemAccount.split("#"); + if (parts.length != 2) { + return null; + } else { + long id = Long.parseLong(parts[0]); + return ContactsContract.Contacts.getLookupUri(id, parts[1]); + } + } + } + + public void setSystemAccount(String account) { + this.systemAccount = account; + } + + public List getGroups() { + ArrayList groups = new ArrayList(); + for (int i = 0; i < this.groups.length(); ++i) { + try { + groups.add(this.groups.getString(i)); + } catch (final JSONException ignored) { + } + } + return groups; + } + + public ArrayList getOtrFingerprints() { + synchronized (this.keys) { + final ArrayList fingerprints = new ArrayList(); + try { + if (this.keys.has("otr_fingerprints")) { + final JSONArray prints = this.keys.getJSONArray("otr_fingerprints"); + for (int i = 0; i < prints.length(); ++i) { + final String print = prints.isNull(i) ? null : prints.getString(i); + if (print != null && !print.isEmpty()) { + fingerprints.add(prints.getString(i)); + } + } + } + } catch (final JSONException ignored) { + + } + return fingerprints; + } + } + + public boolean addOtrFingerprint(String print) { + synchronized (this.keys) { + if (getOtrFingerprints().contains(print)) { + return false; + } + try { + JSONArray fingerprints; + if (!this.keys.has("otr_fingerprints")) { + fingerprints = new JSONArray(); + } else { + fingerprints = this.keys.getJSONArray("otr_fingerprints"); + } + fingerprints.put(print); + this.keys.put("otr_fingerprints", fingerprints); + return true; + } catch (final JSONException ignored) { + return false; + } + } + } + + public long getPgpKeyId() { + synchronized (this.keys) { + if (this.keys.has("pgp_keyid")) { + try { + return this.keys.getLong("pgp_keyid"); + } catch (JSONException e) { + return 0; + } + } else { + return 0; + } + } + } + + public void setPgpKeyId(long keyId) { + synchronized (this.keys) { + try { + this.keys.put("pgp_keyid", keyId); + } catch (final JSONException ignored) { + } + } + } + + public void setOption(int option) { + this.subscription |= 1 << option; + } + + public void resetOption(int option) { + this.subscription &= ~(1 << option); + } + + public boolean getOption(int option) { + return ((this.subscription & (1 << option)) != 0); + } + + public boolean showInRoster() { + return (this.getOption(Contact.Options.IN_ROSTER) && (!this + .getOption(Contact.Options.DIRTY_DELETE))) + || (this.getOption(Contact.Options.DIRTY_PUSH)); + } + + public void parseSubscriptionFromElement(Element item) { + String ask = item.getAttribute("ask"); + String subscription = item.getAttribute("subscription"); + + if (subscription != null) { + switch (subscription) { + case "to": + this.resetOption(Options.FROM); + this.setOption(Options.TO); + break; + case "from": + this.resetOption(Options.TO); + this.setOption(Options.FROM); + this.resetOption(Options.PREEMPTIVE_GRANT); + this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST); + break; + case "both": + this.setOption(Options.TO); + this.setOption(Options.FROM); + this.resetOption(Options.PREEMPTIVE_GRANT); + this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST); + break; + case "none": + this.resetOption(Options.FROM); + this.resetOption(Options.TO); + break; + } + } + + // do NOT override asking if pending push request + if (!this.getOption(Contact.Options.DIRTY_PUSH)) { + if ((ask != null) && (ask.equals("subscribe"))) { + this.setOption(Contact.Options.ASKING); + } else { + this.resetOption(Contact.Options.ASKING); + } + } + } + + public void parseGroupsFromElement(Element item) { + this.groups = new JSONArray(); + for (Element element : item.getChildren()) { + if (element.getName().equals("group") && element.getContent() != null) { + this.groups.put(element.getContent()); + } + } + } + + public Element asElement() { + final Element item = new Element("item"); + item.setAttribute("jid", this.jid.toString()); + if (this.serverName != null) { + item.setAttribute("name", this.serverName); + } + for (String group : getGroups()) { + item.addChild("group").setContent(group); + } + return item; + } + + @Override + public int compareTo(final ListItem another) { + return this.getDisplayName().compareToIgnoreCase( + another.getDisplayName()); + } + + public Jid getServer() { + return getJid().toDomainJid(); + } + + public boolean setAvatar(Avatar avatar) { + if (this.avatar != null && this.avatar.equals(avatar)) { + return false; + } else { + if (this.avatar != null && this.avatar.origin == Avatar.Origin.PEP && avatar.origin == Avatar.Origin.VCARD) { + return false; + } + this.avatar = avatar; + return true; + } + } + + public String getAvatar() { + return avatar == null ? null : avatar.getFilename(); + } + + public boolean deleteOtrFingerprint(String fingerprint) { + synchronized (this.keys) { + boolean success = false; + try { + if (this.keys.has("otr_fingerprints")) { + JSONArray newPrints = new JSONArray(); + JSONArray oldPrints = this.keys + .getJSONArray("otr_fingerprints"); + for (int i = 0; i < oldPrints.length(); ++i) { + if (!oldPrints.getString(i).equals(fingerprint)) { + newPrints.put(oldPrints.getString(i)); + } else { + success = true; + } + } + this.keys.put("otr_fingerprints", newPrints); + } + return success; + } catch (JSONException e) { + return false; + } + } + } + + public boolean mutualPresenceSubscription() { + return getOption(Options.FROM) && getOption(Options.TO); + } + + @Override + public boolean isBlocked() { + return getAccount().isBlocked(this); + } + + @Override + public boolean isDomainBlocked() { + return getAccount().isBlocked(this.getJid().toDomainJid()); + } + + @Override + public Jid getBlockedJid() { + if (isDomainBlocked()) { + return getJid().toDomainJid(); + } else { + return getJid(); + } + } + + public boolean isSelf() { + return account.getJid().toBareJid().equals(getJid().toBareJid()); + } + + public void setCommonName(String cn) { + this.commonName = cn; + } + + public void flagActive() { + this.mActive = true; + } + + public void flagInactive() { + this.mActive = false; + } + + public boolean isActive() { + return this.mActive; + } + + public void setLastseen(long timestamp) { + this.mLastseen = Math.max(timestamp, mLastseen); + } + + public long getLastseen() { + return this.mLastseen; + } + + public void setLastResource(String resource) { + this.mLastPresence = resource; + } + + public String getLastResource() { + return this.mLastPresence; + } + + public final class Options { + public static final int TO = 0; + public static final int FROM = 1; + public static final int ASKING = 2; + public static final int PREEMPTIVE_GRANT = 3; + public static final int IN_ROSTER = 4; + public static final int PENDING_SUBSCRIPTION_REQUEST = 5; + public static final int DIRTY_PUSH = 6; + public static final int DIRTY_DELETE = 7; + } } diff --git a/src/main/java/de/pixart/messenger/entities/Conversation.java b/src/main/java/de/pixart/messenger/entities/Conversation.java index 5a3dd4cf4..f9932f67d 100644 --- a/src/main/java/de/pixart/messenger/entities/Conversation.java +++ b/src/main/java/de/pixart/messenger/entities/Conversation.java @@ -29,948 +29,948 @@ import de.pixart.messenger.xmpp.jid.Jid; public class Conversation extends AbstractEntity implements Blockable, Comparable { - public static final String TABLENAME = "conversations"; - - public static final int STATUS_AVAILABLE = 0; - public static final int STATUS_ARCHIVED = 1; - public static final int STATUS_DELETED = 2; - - public static final int MODE_MULTI = 1; - public static final int MODE_SINGLE = 0; - - public static final String NAME = "name"; - public static final String ACCOUNT = "accountUuid"; - public static final String CONTACT = "contactUuid"; - public static final String CONTACTJID = "contactJid"; - public static final String STATUS = "status"; - public static final String CREATED = "created"; - public static final String MODE = "mode"; - public static final String ATTRIBUTES = "attributes"; - - public static final String ATTRIBUTE_NEXT_ENCRYPTION = "next_encryption"; - public static final String ATTRIBUTE_MUC_PASSWORD = "muc_password"; - public static final String ATTRIBUTE_MUTED_TILL = "muted_till"; - public static final String ATTRIBUTE_ALWAYS_NOTIFY = "always_notify"; - public static final String ATTRIBUTE_CRYPTO_TARGETS = "crypto_targets"; - public static final String ATTRIBUTE_LAST_CLEAR_HISTORY = "last_clear_history"; - - private String name; - private String contactUuid; - private String accountUuid; - private Jid contactJid; - private int status; - private long created; - private int mode; - - private JSONObject attributes = new JSONObject(); - - private Jid nextCounterpart; - - protected final ArrayList messages = new ArrayList<>(); - protected Account account = null; - - private transient SessionImpl otrSession; - - private transient String otrFingerprint = null; - private Smp mSmp = new Smp(); - - private String nextMessage; - - private transient MucOptions mucOptions = null; - - private byte[] symmetricKey; - - private Bookmark bookmark; - - private boolean messagesLeftOnServer = true; - private ChatState mOutgoingChatState = Config.DEFAULT_CHATSTATE; - private ChatState mIncomingChatState = Config.DEFAULT_CHATSTATE; - private String mLastReceivedOtrMessageId = null; - private String mFirstMamReference = null; - private Message correctingMessage; - - public boolean hasMessagesLeftOnServer() { - return messagesLeftOnServer; - } - - public void setHasMessagesLeftOnServer(boolean value) { - this.messagesLeftOnServer = value; - } - - - public Message getFirstUnreadMessage() { - Message first = null; - synchronized (this.messages) { - for (int i = messages.size() - 1; i >= 0; --i) { - if (messages.get(i).isRead()) { - return first; - } else { - first = messages.get(i); - } - } - } - return first; - } - - public Message findUnsentMessageWithUuid(String uuid) { - synchronized(this.messages) { - for (final Message message : this.messages) { - final int s = message.getStatus(); - if ((s == Message.STATUS_UNSEND || s == Message.STATUS_WAITING) && message.getUuid().equals(uuid)) { - return message; - } - } - } - return null; - } - - public void findWaitingMessages(OnMessageFound onMessageFound) { - synchronized (this.messages) { - for(Message message : this.messages) { - if (message.getStatus() == Message.STATUS_WAITING) { - onMessageFound.onMessageFound(message); - } - } - } - } - - public void findUnreadMessages(OnMessageFound onMessageFound) { - synchronized (this.messages) { - for(Message message : this.messages) { - if (!message.isRead()) { - onMessageFound.onMessageFound(message); - } - } - } - } - - public void findMessagesWithFiles(final OnMessageFound onMessageFound) { - synchronized (this.messages) { - for (final Message message : this.messages) { - if ((message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) - && message.getEncryption() != Message.ENCRYPTION_PGP) { - onMessageFound.onMessageFound(message); - } - } - } - } - - public Message findMessageWithFileAndUuid(final String uuid) { - synchronized (this.messages) { - for (final Message message : this.messages) { - if (message.getUuid().equals(uuid) - && message.getEncryption() != Message.ENCRYPTION_PGP - && (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || message.treatAsDownloadable() != Message.Decision.NEVER)) { - return message; - } - } - } - return null; - } - - public void clearMessages() { - synchronized (this.messages) { - this.messages.clear(); - } - } - - public boolean setIncomingChatState(ChatState state) { - if (this.mIncomingChatState == state) { - return false; - } - this.mIncomingChatState = state; - return true; - } - - public ChatState getIncomingChatState() { - return this.mIncomingChatState; - } - - public boolean setOutgoingChatState(ChatState state) { - if (mode == MODE_MULTI) { - return false; - } - if (this.mOutgoingChatState != state) { - this.mOutgoingChatState = state; - return true; - } else { - return false; - } - } - - public ChatState getOutgoingChatState() { - return this.mOutgoingChatState; - } - - public void trim() { - synchronized (this.messages) { - final int size = messages.size(); - final int maxsize = Config.PAGE_SIZE * Config.MAX_NUM_PAGES; - if (size > maxsize) { - List discards = this.messages.subList(0, size - maxsize); - final PgpDecryptionService pgpDecryptionService = account.getPgpDecryptionService(); - if (pgpDecryptionService != null) { - pgpDecryptionService.discard(discards); - } - discards.clear(); - untieMessages(); - } - } - } - - public void findUnsentMessagesWithEncryption(int encryptionType, OnMessageFound onMessageFound) { - synchronized (this.messages) { - for (Message message : this.messages) { - if ((message.getStatus() == Message.STATUS_UNSEND || message.getStatus() == Message.STATUS_WAITING) - && (message.getEncryption() == encryptionType)) { - onMessageFound.onMessageFound(message); - } - } - } - } - - public void findUnsentTextMessages(OnMessageFound onMessageFound) { - synchronized (this.messages) { - for (Message message : this.messages) { - if (message.getType() != Message.TYPE_IMAGE - && message.getStatus() == Message.STATUS_UNSEND) { - onMessageFound.onMessageFound(message); - } - } - } - } - - public Message findSentMessageWithUuidOrRemoteId(String id) { - synchronized (this.messages) { - for (Message message : this.messages) { - if (id.equals(message.getUuid()) - || (message.getStatus() >= Message.STATUS_SEND - && id.equals(message.getRemoteMsgId()))) { - return message; - } - } - } - return null; - } - - public Message findMessageWithRemoteIdAndCounterpart(String id, Jid counterpart, boolean received, boolean carbon) { - synchronized (this.messages) { - for(int i = this.messages.size() - 1; i >= 0; --i) { - Message message = messages.get(i); - if (counterpart.equals(message.getCounterpart()) - && ((message.getStatus() == Message.STATUS_RECEIVED) == received) - && (carbon == message.isCarbon() || received) ) { - if (id.equals(message.getRemoteMsgId())) { - return message; - } else { - return null; - } - } - } - } - return null; - } - - public Message findSentMessageWithUuid(String id) { - synchronized (this.messages) { - for (Message message : this.messages) { - if (id.equals(message.getUuid())) { - return message; - } - } - } - return null; - } - - public void populateWithMessages(final List messages) { - synchronized (this.messages) { - messages.clear(); - messages.addAll(this.messages); - } - for(Iterator iterator = messages.iterator(); iterator.hasNext();) { - if (iterator.next().wasMergedIntoPrevious()) { - iterator.remove(); - } - } - } - - @Override - public boolean isBlocked() { - return getContact().isBlocked(); - } - - @Override - public boolean isDomainBlocked() { - return getContact().isDomainBlocked(); - } - - @Override - public Jid getBlockedJid() { - return getContact().getBlockedJid(); - } - - public String getLastReceivedOtrMessageId() { - return this.mLastReceivedOtrMessageId; - } - - public void setLastReceivedOtrMessageId(String id) { - this.mLastReceivedOtrMessageId = id; - } - - public int countMessages() { - synchronized (this.messages) { - return this.messages.size(); - } - } - - public void setFirstMamReference(String reference) { - this.mFirstMamReference = reference; - } - - public String getFirstMamReference() { - return this.mFirstMamReference; - } - - public void setLastClearHistory(long time) { - setAttribute("ATTRIBUTE_LAST_CLEAR_HISTORY",String.valueOf(time)); - } - - public long getLastClearHistory() { - return getLongAttribute("ATTRIBUTE_LAST_CLEAR_HISTORY", 0); - } - - public List getAcceptedCryptoTargets() { - if (mode == MODE_SINGLE) { - return Arrays.asList(getJid().toBareJid()); - } else { - return getJidListAttribute(ATTRIBUTE_CRYPTO_TARGETS); - } - } - - public void setAcceptedCryptoTargets(List acceptedTargets) { - setAttribute(ATTRIBUTE_CRYPTO_TARGETS, acceptedTargets); - } - - public void setCorrectingMessage(Message correctingMessage) { - this.correctingMessage = correctingMessage; - } - - public Message getCorrectingMessage() { - return this.correctingMessage; - } - - public boolean withSelf() { - return getContact().isSelf(); - } - - @Override - public int compareTo(Conversation another) { - final Message left = getLatestMessage(); - final Message right = another.getLatestMessage(); - if (left.getTimeSent() > right.getTimeSent()) { - return -1; - } else if (left.getTimeSent() < right.getTimeSent()) { - return 1; - } else { - return 0; - } - } - - public interface OnMessageFound { - void onMessageFound(final Message message); - } - - public Conversation(final String name, final Account account, final Jid contactJid, - final int mode) { - this(java.util.UUID.randomUUID().toString(), name, null, account - .getUuid(), contactJid, System.currentTimeMillis(), - STATUS_AVAILABLE, mode, ""); - this.account = account; - } - - public Conversation(final String uuid, final String name, final String contactUuid, - final String accountUuid, final Jid contactJid, final long created, final int status, - final int mode, final String attributes) { - this.uuid = uuid; - this.name = name; - this.contactUuid = contactUuid; - this.accountUuid = accountUuid; - this.contactJid = contactJid; - this.created = created; - this.status = status; - this.mode = mode; - try { - this.attributes = new JSONObject(attributes == null ? "" : attributes); - } catch (JSONException e) { - this.attributes = new JSONObject(); - } - } - - public boolean isRead() { - return (this.messages.size() == 0) || this.messages.get(this.messages.size() - 1).isRead(); - } - - public List markRead() { - final List unread = new ArrayList<>(); - synchronized (this.messages) { - for(Message message : this.messages) { - if (!message.isRead()) { - message.markRead(); - unread.add(message); - } - } - } - return unread; - } - - public Message getLatestMarkableMessage() { - for (int i = this.messages.size() - 1; i >= 0; --i) { - if (this.messages.get(i).getStatus() <= Message.STATUS_RECEIVED - && this.messages.get(i).markable) { - if (this.messages.get(i).isRead()) { - return null; - } else { - return this.messages.get(i); - } - } - } - return null; - } - - public Message getLatestMessage() { - if (this.messages.size() == 0) { - Message message = new Message(this, "", Message.ENCRYPTION_NONE); - message.setTime(getCreated()); - return message; - } else { - Message message = this.messages.get(this.messages.size() - 1); - message.setConversation(this); - return message; - } - } - - public String getName() { - if (getMode() == MODE_MULTI) { - if (getMucOptions().getSubject() != null) { - return getMucOptions().getSubject(); - } else if (bookmark != null - && bookmark.getBookmarkName() != null - && !bookmark.getBookmarkName().trim().isEmpty()) { - return bookmark.getBookmarkName().trim(); - } else { - String generatedName = getMucOptions().createNameFromParticipants(); - if (generatedName != null) { - return generatedName; - } else { - return getJid().getLocalpart(); - } - } - } else { - return this.getContact().getDisplayName(); - } - } - - public String getParticipants() { - if (getMode() == MODE_MULTI) { - String generatedName = getMucOptions().createNameFromParticipants(); - if (generatedName != null) { - return generatedName; - } else { - return null; - } - } else { - return null; - } - } - - public String getAccountUuid() { - return this.accountUuid; - } - - public Account getAccount() { - return this.account; - } - - public Contact getContact() { - return this.account.getRoster().getContact(this.contactJid); - } - - public void setAccount(final Account account) { - this.account = account; - } - - @Override - public Jid getJid() { - return this.contactJid; - } - - public int getStatus() { - return this.status; - } - - public long getCreated() { - return this.created; - } - - public ContentValues getContentValues() { - ContentValues values = new ContentValues(); - values.put(UUID, uuid); - values.put(NAME, name); - values.put(CONTACT, contactUuid); - values.put(ACCOUNT, accountUuid); - values.put(CONTACTJID, contactJid.toPreppedString()); - values.put(CREATED, created); - values.put(STATUS, status); - values.put(MODE, mode); - values.put(ATTRIBUTES, attributes.toString()); - return values; - } - - public static Conversation fromCursor(Cursor cursor) { - Jid jid; - try { - jid = Jid.fromString(cursor.getString(cursor.getColumnIndex(CONTACTJID)), true); - } catch (final InvalidJidException e) { - // Borked DB.. - jid = null; - } - return new Conversation(cursor.getString(cursor.getColumnIndex(UUID)), - cursor.getString(cursor.getColumnIndex(NAME)), - cursor.getString(cursor.getColumnIndex(CONTACT)), - cursor.getString(cursor.getColumnIndex(ACCOUNT)), - jid, - cursor.getLong(cursor.getColumnIndex(CREATED)), - cursor.getInt(cursor.getColumnIndex(STATUS)), - cursor.getInt(cursor.getColumnIndex(MODE)), - cursor.getString(cursor.getColumnIndex(ATTRIBUTES))); - } - - public void setStatus(int status) { - this.status = status; - } - - public int getMode() { - return this.mode; - } - - public void setMode(int mode) { - this.mode = mode; - } - - public SessionImpl startOtrSession(String presence, boolean sendStart) { - if (this.otrSession != null) { - return this.otrSession; - } else { - final SessionID sessionId = new SessionID(this.getJid().toBareJid().toString(), - presence, - "xmpp"); - this.otrSession = new SessionImpl(sessionId, getAccount().getOtrService()); - try { - if (sendStart) { - this.otrSession.startSession(); - return this.otrSession; - } - return this.otrSession; - } catch (OtrException e) { - return null; - } - } - - } - - public SessionImpl getOtrSession() { - return this.otrSession; - } - - public void resetOtrSession() { - this.otrFingerprint = null; - this.otrSession = null; - this.mSmp.hint = null; - this.mSmp.secret = null; - this.mSmp.status = Smp.STATUS_NONE; - } - - public Smp smp() { - return mSmp; - } - - public boolean startOtrIfNeeded() { - if (this.otrSession != null && this.otrSession.getSessionStatus() != SessionStatus.ENCRYPTED) { - try { - this.otrSession.startSession(); - return true; - } catch (OtrException e) { - this.resetOtrSession(); - return false; - } - } else { - return true; - } - } - - public boolean endOtrIfNeeded() { - if (this.otrSession != null) { - if (this.otrSession.getSessionStatus() == SessionStatus.ENCRYPTED) { - try { - this.otrSession.endSession(); - this.resetOtrSession(); - return true; - } catch (OtrException e) { - this.resetOtrSession(); - return false; - } - } else { - this.resetOtrSession(); - return false; - } - } else { - return false; - } - } - - public boolean hasValidOtrSession() { - return this.otrSession != null; - } - - public synchronized String getOtrFingerprint() { - if (this.otrFingerprint == null) { - try { - if (getOtrSession() == null || getOtrSession().getSessionStatus() != SessionStatus.ENCRYPTED) { - return null; - } - DSAPublicKey remotePubKey = (DSAPublicKey) getOtrSession().getRemotePublicKey(); - this.otrFingerprint = getAccount().getOtrService().getFingerprint(remotePubKey); - } catch (final OtrCryptoException | UnsupportedOperationException ignored) { - return null; - } - } - return this.otrFingerprint; - } - - public boolean verifyOtrFingerprint() { - final String fingerprint = getOtrFingerprint(); - if (fingerprint != null) { - getContact().addOtrFingerprint(fingerprint); - return true; - } else { - return false; - } - } - - public boolean isOtrFingerprintVerified() { - return getContact().getOtrFingerprints().contains(getOtrFingerprint()); - } - - /** - * short for is Private and Non-anonymous - */ - private boolean isPnNA() { - return mode == MODE_SINGLE || (getMucOptions().membersOnly() && getMucOptions().nonanonymous()); - } - - public synchronized MucOptions getMucOptions() { - if (this.mucOptions == null) { - this.mucOptions = new MucOptions(this); - } - return this.mucOptions; - } - - public void resetMucOptions() { - this.mucOptions = null; - } - - public void setContactJid(final Jid jid) { - this.contactJid = jid; - } - - public void setNextCounterpart(Jid jid) { - this.nextCounterpart = jid; - } - - public Jid getNextCounterpart() { - return this.nextCounterpart; - } - - private int getMostRecentlyUsedIncomingEncryption() { - synchronized (this.messages) { - for(int i = this.messages.size() -1; i >= 0; --i) { - final Message m = this.messages.get(i); - if (m.getStatus() == Message.STATUS_RECEIVED) { - final int e = m.getEncryption(); - if (e == Message.ENCRYPTION_DECRYPTED || e == Message.ENCRYPTION_DECRYPTION_FAILED) { - return Message.ENCRYPTION_PGP; - } else { - return e; - } - } - } - } - return Message.ENCRYPTION_NONE; - } - - public int getNextEncryption() { - return Math.max(this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, Message.ENCRYPTION_NONE), Message.ENCRYPTION_NONE); - } - - public void setNextEncryption(int encryption) { - this.setAttribute(ATTRIBUTE_NEXT_ENCRYPTION, String.valueOf(encryption)); - } - - public String getNextMessage() { - if (this.nextMessage == null) { - return ""; - } else { - return this.nextMessage; - } - } - - public boolean smpRequested() { - return smp().status == Smp.STATUS_CONTACT_REQUESTED; - } - - public void setNextMessage(String message) { - this.nextMessage = message; - } - - public void setSymmetricKey(byte[] key) { - this.symmetricKey = key; - } - - public byte[] getSymmetricKey() { - return this.symmetricKey; - } - - public void setBookmark(Bookmark bookmark) { - this.bookmark = bookmark; - this.bookmark.setConversation(this); - } - - public void deregisterWithBookmark() { - if (this.bookmark != null) { - this.bookmark.setConversation(null); - } - } - - public Bookmark getBookmark() { - return this.bookmark; - } - - public boolean hasDuplicateMessage(Message message) { - synchronized (this.messages) { - for (int i = this.messages.size() - 1; i >= 0; --i) { - if (this.messages.get(i).similar(message)) { - return true; - } - } - } - return false; - } - - public Message findSentMessageWithBody(String body) { - synchronized (this.messages) { - for (int i = this.messages.size() - 1; i >= 0; --i) { - Message message = this.messages.get(i); - if (message.getStatus() == Message.STATUS_UNSEND || message.getStatus() == Message.STATUS_SEND) { - String otherBody; - if (message.hasFileOnRemoteHost()) { - otherBody = message.getFileParams().url.toString(); - } else { - otherBody = message.body; - } - if (otherBody != null && otherBody.equals(body)) { - return message; - } - } - } - return null; - } - } - - public long getLastMessageTransmitted() { - final long last_clear = getLastClearHistory(); - long last_received = 0; - synchronized (this.messages) { - for(int i = this.messages.size() - 1; i >= 0; --i) { - Message message = this.messages.get(i); - if (message.getStatus() == Message.STATUS_RECEIVED || message.isCarbon()) { - last_received = message.getTimeSent(); - break; - } - } - } - return Math.max(last_clear,last_received); - } - - public void setMutedTill(long value) { - this.setAttribute(ATTRIBUTE_MUTED_TILL, String.valueOf(value)); - } - - public boolean isMuted() { - return System.currentTimeMillis() < this.getLongAttribute(ATTRIBUTE_MUTED_TILL, 0); - } - - public boolean alwaysNotify() { - return mode == MODE_SINGLE || getBooleanAttribute(ATTRIBUTE_ALWAYS_NOTIFY, Config.ALWAYS_NOTIFY_BY_DEFAULT || isPnNA()); - } - - public boolean setAttribute(String key, String value) { - synchronized (this.attributes) { - try { - this.attributes.put(key, value); - return true; - } catch (JSONException e) { - return false; - } - } - } - - public boolean setAttribute(String key, List jids) { - JSONArray array = new JSONArray(); - for(Jid jid : jids) { - array.put(jid.toBareJid().toString()); - } - synchronized (this.attributes) { - try { - this.attributes.put(key, array); - return true; - } catch (JSONException e) { - e.printStackTrace(); - return false; - } - } - } - - public String getAttribute(String key) { - synchronized (this.attributes) { - try { - return this.attributes.getString(key); - } catch (JSONException e) { - return null; - } - } - } - - public List getJidListAttribute(String key) { - ArrayList list = new ArrayList<>(); - synchronized (this.attributes) { - try { - JSONArray array = this.attributes.getJSONArray(key); - for (int i = 0; i < array.length(); ++i) { - try { - list.add(Jid.fromString(array.getString(i))); - } catch (InvalidJidException e) { - //ignored - } - } - } catch (JSONException e) { - //ignored - } - } - return list; - } - - public int getIntAttribute(String key, int defaultValue) { - String value = this.getAttribute(key); - if (value == null) { - return defaultValue; - } else { - try { - return Integer.parseInt(value); - } catch (NumberFormatException e) { - return defaultValue; - } - } - } - - public long getLongAttribute(String key, long defaultValue) { - String value = this.getAttribute(key); - if (value == null) { - return defaultValue; - } else { - try { - return Long.parseLong(value); - } catch (NumberFormatException e) { - return defaultValue; - } - } - } - - public boolean getBooleanAttribute(String key, boolean defaultValue) { - String value = this.getAttribute(key); - if (value == null) { - return defaultValue; - } else { - return Boolean.parseBoolean(value); - } - } - - public void add(Message message) { - message.setConversation(this); - synchronized (this.messages) { - this.messages.add(message); - } - } - - public void prepend(Message message) { - message.setConversation(this); - synchronized (this.messages) { - this.messages.add(0,message); - } - } - - public void addAll(int index, List messages) { - synchronized (this.messages) { - this.messages.addAll(index, messages); - } - account.getPgpDecryptionService().decrypt(messages); - } - - public void sort() { - synchronized (this.messages) { - Collections.sort(this.messages, new Comparator() { - @Override - public int compare(Message left, Message right) { - if (left.getTimeSent() < right.getTimeSent()) { - return -1; - } else if (left.getTimeSent() > right.getTimeSent()) { - return 1; - } else { - return 0; - } - } - }); - untieMessages(); - } - } - - private void untieMessages() { - for(Message message : this.messages) { - message.untie(); - } - } - - public int unreadCount() { - synchronized (this.messages) { - int count = 0; - for(int i = this.messages.size() - 1; i >= 0; --i) { - if (this.messages.get(i).isRead()) { - return count; - } - ++count; - } - return count; - } - } - - public class Smp { - public static final int STATUS_NONE = 0; - public static final int STATUS_CONTACT_REQUESTED = 1; - public static final int STATUS_WE_REQUESTED = 2; - public static final int STATUS_FAILED = 3; - public static final int STATUS_VERIFIED = 4; - - public String secret = null; - public String hint = null; - public int status = 0; - } + public static final String TABLENAME = "conversations"; + + public static final int STATUS_AVAILABLE = 0; + public static final int STATUS_ARCHIVED = 1; + public static final int STATUS_DELETED = 2; + + public static final int MODE_MULTI = 1; + public static final int MODE_SINGLE = 0; + + public static final String NAME = "name"; + public static final String ACCOUNT = "accountUuid"; + public static final String CONTACT = "contactUuid"; + public static final String CONTACTJID = "contactJid"; + public static final String STATUS = "status"; + public static final String CREATED = "created"; + public static final String MODE = "mode"; + public static final String ATTRIBUTES = "attributes"; + + public static final String ATTRIBUTE_NEXT_ENCRYPTION = "next_encryption"; + public static final String ATTRIBUTE_MUC_PASSWORD = "muc_password"; + public static final String ATTRIBUTE_MUTED_TILL = "muted_till"; + public static final String ATTRIBUTE_ALWAYS_NOTIFY = "always_notify"; + public static final String ATTRIBUTE_CRYPTO_TARGETS = "crypto_targets"; + public static final String ATTRIBUTE_LAST_CLEAR_HISTORY = "last_clear_history"; + + private String name; + private String contactUuid; + private String accountUuid; + private Jid contactJid; + private int status; + private long created; + private int mode; + + private JSONObject attributes = new JSONObject(); + + private Jid nextCounterpart; + + protected final ArrayList messages = new ArrayList<>(); + protected Account account = null; + + private transient SessionImpl otrSession; + + private transient String otrFingerprint = null; + private Smp mSmp = new Smp(); + + private String nextMessage; + + private transient MucOptions mucOptions = null; + + private byte[] symmetricKey; + + private Bookmark bookmark; + + private boolean messagesLeftOnServer = true; + private ChatState mOutgoingChatState = Config.DEFAULT_CHATSTATE; + private ChatState mIncomingChatState = Config.DEFAULT_CHATSTATE; + private String mLastReceivedOtrMessageId = null; + private String mFirstMamReference = null; + private Message correctingMessage; + + public boolean hasMessagesLeftOnServer() { + return messagesLeftOnServer; + } + + public void setHasMessagesLeftOnServer(boolean value) { + this.messagesLeftOnServer = value; + } + + + public Message getFirstUnreadMessage() { + Message first = null; + synchronized (this.messages) { + for (int i = messages.size() - 1; i >= 0; --i) { + if (messages.get(i).isRead()) { + return first; + } else { + first = messages.get(i); + } + } + } + return first; + } + + public Message findUnsentMessageWithUuid(String uuid) { + synchronized (this.messages) { + for (final Message message : this.messages) { + final int s = message.getStatus(); + if ((s == Message.STATUS_UNSEND || s == Message.STATUS_WAITING) && message.getUuid().equals(uuid)) { + return message; + } + } + } + return null; + } + + public void findWaitingMessages(OnMessageFound onMessageFound) { + synchronized (this.messages) { + for (Message message : this.messages) { + if (message.getStatus() == Message.STATUS_WAITING) { + onMessageFound.onMessageFound(message); + } + } + } + } + + public void findUnreadMessages(OnMessageFound onMessageFound) { + synchronized (this.messages) { + for (Message message : this.messages) { + if (!message.isRead()) { + onMessageFound.onMessageFound(message); + } + } + } + } + + public void findMessagesWithFiles(final OnMessageFound onMessageFound) { + synchronized (this.messages) { + for (final Message message : this.messages) { + if ((message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) + && message.getEncryption() != Message.ENCRYPTION_PGP) { + onMessageFound.onMessageFound(message); + } + } + } + } + + public Message findMessageWithFileAndUuid(final String uuid) { + synchronized (this.messages) { + for (final Message message : this.messages) { + if (message.getUuid().equals(uuid) + && message.getEncryption() != Message.ENCRYPTION_PGP + && (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || message.treatAsDownloadable() != Message.Decision.NEVER)) { + return message; + } + } + } + return null; + } + + public void clearMessages() { + synchronized (this.messages) { + this.messages.clear(); + } + } + + public boolean setIncomingChatState(ChatState state) { + if (this.mIncomingChatState == state) { + return false; + } + this.mIncomingChatState = state; + return true; + } + + public ChatState getIncomingChatState() { + return this.mIncomingChatState; + } + + public boolean setOutgoingChatState(ChatState state) { + if (mode == MODE_MULTI) { + return false; + } + if (this.mOutgoingChatState != state) { + this.mOutgoingChatState = state; + return true; + } else { + return false; + } + } + + public ChatState getOutgoingChatState() { + return this.mOutgoingChatState; + } + + public void trim() { + synchronized (this.messages) { + final int size = messages.size(); + final int maxsize = Config.PAGE_SIZE * Config.MAX_NUM_PAGES; + if (size > maxsize) { + List discards = this.messages.subList(0, size - maxsize); + final PgpDecryptionService pgpDecryptionService = account.getPgpDecryptionService(); + if (pgpDecryptionService != null) { + pgpDecryptionService.discard(discards); + } + discards.clear(); + untieMessages(); + } + } + } + + public void findUnsentMessagesWithEncryption(int encryptionType, OnMessageFound onMessageFound) { + synchronized (this.messages) { + for (Message message : this.messages) { + if ((message.getStatus() == Message.STATUS_UNSEND || message.getStatus() == Message.STATUS_WAITING) + && (message.getEncryption() == encryptionType)) { + onMessageFound.onMessageFound(message); + } + } + } + } + + public void findUnsentTextMessages(OnMessageFound onMessageFound) { + synchronized (this.messages) { + for (Message message : this.messages) { + if (message.getType() != Message.TYPE_IMAGE + && message.getStatus() == Message.STATUS_UNSEND) { + onMessageFound.onMessageFound(message); + } + } + } + } + + public Message findSentMessageWithUuidOrRemoteId(String id) { + synchronized (this.messages) { + for (Message message : this.messages) { + if (id.equals(message.getUuid()) + || (message.getStatus() >= Message.STATUS_SEND + && id.equals(message.getRemoteMsgId()))) { + return message; + } + } + } + return null; + } + + public Message findMessageWithRemoteIdAndCounterpart(String id, Jid counterpart, boolean received, boolean carbon) { + synchronized (this.messages) { + for (int i = this.messages.size() - 1; i >= 0; --i) { + Message message = messages.get(i); + if (counterpart.equals(message.getCounterpart()) + && ((message.getStatus() == Message.STATUS_RECEIVED) == received) + && (carbon == message.isCarbon() || received)) { + if (id.equals(message.getRemoteMsgId())) { + return message; + } else { + return null; + } + } + } + } + return null; + } + + public Message findSentMessageWithUuid(String id) { + synchronized (this.messages) { + for (Message message : this.messages) { + if (id.equals(message.getUuid())) { + return message; + } + } + } + return null; + } + + public void populateWithMessages(final List messages) { + synchronized (this.messages) { + messages.clear(); + messages.addAll(this.messages); + } + for (Iterator iterator = messages.iterator(); iterator.hasNext(); ) { + if (iterator.next().wasMergedIntoPrevious()) { + iterator.remove(); + } + } + } + + @Override + public boolean isBlocked() { + return getContact().isBlocked(); + } + + @Override + public boolean isDomainBlocked() { + return getContact().isDomainBlocked(); + } + + @Override + public Jid getBlockedJid() { + return getContact().getBlockedJid(); + } + + public String getLastReceivedOtrMessageId() { + return this.mLastReceivedOtrMessageId; + } + + public void setLastReceivedOtrMessageId(String id) { + this.mLastReceivedOtrMessageId = id; + } + + public int countMessages() { + synchronized (this.messages) { + return this.messages.size(); + } + } + + public void setFirstMamReference(String reference) { + this.mFirstMamReference = reference; + } + + public String getFirstMamReference() { + return this.mFirstMamReference; + } + + public void setLastClearHistory(long time) { + setAttribute("ATTRIBUTE_LAST_CLEAR_HISTORY", String.valueOf(time)); + } + + public long getLastClearHistory() { + return getLongAttribute("ATTRIBUTE_LAST_CLEAR_HISTORY", 0); + } + + public List getAcceptedCryptoTargets() { + if (mode == MODE_SINGLE) { + return Arrays.asList(getJid().toBareJid()); + } else { + return getJidListAttribute(ATTRIBUTE_CRYPTO_TARGETS); + } + } + + public void setAcceptedCryptoTargets(List acceptedTargets) { + setAttribute(ATTRIBUTE_CRYPTO_TARGETS, acceptedTargets); + } + + public void setCorrectingMessage(Message correctingMessage) { + this.correctingMessage = correctingMessage; + } + + public Message getCorrectingMessage() { + return this.correctingMessage; + } + + public boolean withSelf() { + return getContact().isSelf(); + } + + @Override + public int compareTo(Conversation another) { + final Message left = getLatestMessage(); + final Message right = another.getLatestMessage(); + if (left.getTimeSent() > right.getTimeSent()) { + return -1; + } else if (left.getTimeSent() < right.getTimeSent()) { + return 1; + } else { + return 0; + } + } + + public interface OnMessageFound { + void onMessageFound(final Message message); + } + + public Conversation(final String name, final Account account, final Jid contactJid, + final int mode) { + this(java.util.UUID.randomUUID().toString(), name, null, account + .getUuid(), contactJid, System.currentTimeMillis(), + STATUS_AVAILABLE, mode, ""); + this.account = account; + } + + public Conversation(final String uuid, final String name, final String contactUuid, + final String accountUuid, final Jid contactJid, final long created, final int status, + final int mode, final String attributes) { + this.uuid = uuid; + this.name = name; + this.contactUuid = contactUuid; + this.accountUuid = accountUuid; + this.contactJid = contactJid; + this.created = created; + this.status = status; + this.mode = mode; + try { + this.attributes = new JSONObject(attributes == null ? "" : attributes); + } catch (JSONException e) { + this.attributes = new JSONObject(); + } + } + + public boolean isRead() { + return (this.messages.size() == 0) || this.messages.get(this.messages.size() - 1).isRead(); + } + + public List markRead() { + final List unread = new ArrayList<>(); + synchronized (this.messages) { + for (Message message : this.messages) { + if (!message.isRead()) { + message.markRead(); + unread.add(message); + } + } + } + return unread; + } + + public Message getLatestMarkableMessage() { + for (int i = this.messages.size() - 1; i >= 0; --i) { + if (this.messages.get(i).getStatus() <= Message.STATUS_RECEIVED + && this.messages.get(i).markable) { + if (this.messages.get(i).isRead()) { + return null; + } else { + return this.messages.get(i); + } + } + } + return null; + } + + public Message getLatestMessage() { + if (this.messages.size() == 0) { + Message message = new Message(this, "", Message.ENCRYPTION_NONE); + message.setTime(getCreated()); + return message; + } else { + Message message = this.messages.get(this.messages.size() - 1); + message.setConversation(this); + return message; + } + } + + public String getName() { + if (getMode() == MODE_MULTI) { + if (getMucOptions().getSubject() != null) { + return getMucOptions().getSubject(); + } else if (bookmark != null + && bookmark.getBookmarkName() != null + && !bookmark.getBookmarkName().trim().isEmpty()) { + return bookmark.getBookmarkName().trim(); + } else { + String generatedName = getMucOptions().createNameFromParticipants(); + if (generatedName != null) { + return generatedName; + } else { + return getJid().getLocalpart(); + } + } + } else { + return this.getContact().getDisplayName(); + } + } + + public String getParticipants() { + if (getMode() == MODE_MULTI) { + String generatedName = getMucOptions().createNameFromParticipants(); + if (generatedName != null) { + return generatedName; + } else { + return null; + } + } else { + return null; + } + } + + public String getAccountUuid() { + return this.accountUuid; + } + + public Account getAccount() { + return this.account; + } + + public Contact getContact() { + return this.account.getRoster().getContact(this.contactJid); + } + + public void setAccount(final Account account) { + this.account = account; + } + + @Override + public Jid getJid() { + return this.contactJid; + } + + public int getStatus() { + return this.status; + } + + public long getCreated() { + return this.created; + } + + public ContentValues getContentValues() { + ContentValues values = new ContentValues(); + values.put(UUID, uuid); + values.put(NAME, name); + values.put(CONTACT, contactUuid); + values.put(ACCOUNT, accountUuid); + values.put(CONTACTJID, contactJid.toPreppedString()); + values.put(CREATED, created); + values.put(STATUS, status); + values.put(MODE, mode); + values.put(ATTRIBUTES, attributes.toString()); + return values; + } + + public static Conversation fromCursor(Cursor cursor) { + Jid jid; + try { + jid = Jid.fromString(cursor.getString(cursor.getColumnIndex(CONTACTJID)), true); + } catch (final InvalidJidException e) { + // Borked DB.. + jid = null; + } + return new Conversation(cursor.getString(cursor.getColumnIndex(UUID)), + cursor.getString(cursor.getColumnIndex(NAME)), + cursor.getString(cursor.getColumnIndex(CONTACT)), + cursor.getString(cursor.getColumnIndex(ACCOUNT)), + jid, + cursor.getLong(cursor.getColumnIndex(CREATED)), + cursor.getInt(cursor.getColumnIndex(STATUS)), + cursor.getInt(cursor.getColumnIndex(MODE)), + cursor.getString(cursor.getColumnIndex(ATTRIBUTES))); + } + + public void setStatus(int status) { + this.status = status; + } + + public int getMode() { + return this.mode; + } + + public void setMode(int mode) { + this.mode = mode; + } + + public SessionImpl startOtrSession(String presence, boolean sendStart) { + if (this.otrSession != null) { + return this.otrSession; + } else { + final SessionID sessionId = new SessionID(this.getJid().toBareJid().toString(), + presence, + "xmpp"); + this.otrSession = new SessionImpl(sessionId, getAccount().getOtrService()); + try { + if (sendStart) { + this.otrSession.startSession(); + return this.otrSession; + } + return this.otrSession; + } catch (OtrException e) { + return null; + } + } + + } + + public SessionImpl getOtrSession() { + return this.otrSession; + } + + public void resetOtrSession() { + this.otrFingerprint = null; + this.otrSession = null; + this.mSmp.hint = null; + this.mSmp.secret = null; + this.mSmp.status = Smp.STATUS_NONE; + } + + public Smp smp() { + return mSmp; + } + + public boolean startOtrIfNeeded() { + if (this.otrSession != null && this.otrSession.getSessionStatus() != SessionStatus.ENCRYPTED) { + try { + this.otrSession.startSession(); + return true; + } catch (OtrException e) { + this.resetOtrSession(); + return false; + } + } else { + return true; + } + } + + public boolean endOtrIfNeeded() { + if (this.otrSession != null) { + if (this.otrSession.getSessionStatus() == SessionStatus.ENCRYPTED) { + try { + this.otrSession.endSession(); + this.resetOtrSession(); + return true; + } catch (OtrException e) { + this.resetOtrSession(); + return false; + } + } else { + this.resetOtrSession(); + return false; + } + } else { + return false; + } + } + + public boolean hasValidOtrSession() { + return this.otrSession != null; + } + + public synchronized String getOtrFingerprint() { + if (this.otrFingerprint == null) { + try { + if (getOtrSession() == null || getOtrSession().getSessionStatus() != SessionStatus.ENCRYPTED) { + return null; + } + DSAPublicKey remotePubKey = (DSAPublicKey) getOtrSession().getRemotePublicKey(); + this.otrFingerprint = getAccount().getOtrService().getFingerprint(remotePubKey); + } catch (final OtrCryptoException | UnsupportedOperationException ignored) { + return null; + } + } + return this.otrFingerprint; + } + + public boolean verifyOtrFingerprint() { + final String fingerprint = getOtrFingerprint(); + if (fingerprint != null) { + getContact().addOtrFingerprint(fingerprint); + return true; + } else { + return false; + } + } + + public boolean isOtrFingerprintVerified() { + return getContact().getOtrFingerprints().contains(getOtrFingerprint()); + } + + /** + * short for is Private and Non-anonymous + */ + private boolean isPnNA() { + return mode == MODE_SINGLE || (getMucOptions().membersOnly() && getMucOptions().nonanonymous()); + } + + public synchronized MucOptions getMucOptions() { + if (this.mucOptions == null) { + this.mucOptions = new MucOptions(this); + } + return this.mucOptions; + } + + public void resetMucOptions() { + this.mucOptions = null; + } + + public void setContactJid(final Jid jid) { + this.contactJid = jid; + } + + public void setNextCounterpart(Jid jid) { + this.nextCounterpart = jid; + } + + public Jid getNextCounterpart() { + return this.nextCounterpart; + } + + private int getMostRecentlyUsedIncomingEncryption() { + synchronized (this.messages) { + for (int i = this.messages.size() - 1; i >= 0; --i) { + final Message m = this.messages.get(i); + if (m.getStatus() == Message.STATUS_RECEIVED) { + final int e = m.getEncryption(); + if (e == Message.ENCRYPTION_DECRYPTED || e == Message.ENCRYPTION_DECRYPTION_FAILED) { + return Message.ENCRYPTION_PGP; + } else { + return e; + } + } + } + } + return Message.ENCRYPTION_NONE; + } + + public int getNextEncryption() { + return Math.max(this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, Message.ENCRYPTION_NONE), Message.ENCRYPTION_NONE); + } + + public void setNextEncryption(int encryption) { + this.setAttribute(ATTRIBUTE_NEXT_ENCRYPTION, String.valueOf(encryption)); + } + + public String getNextMessage() { + if (this.nextMessage == null) { + return ""; + } else { + return this.nextMessage; + } + } + + public boolean smpRequested() { + return smp().status == Smp.STATUS_CONTACT_REQUESTED; + } + + public void setNextMessage(String message) { + this.nextMessage = message; + } + + public void setSymmetricKey(byte[] key) { + this.symmetricKey = key; + } + + public byte[] getSymmetricKey() { + return this.symmetricKey; + } + + public void setBookmark(Bookmark bookmark) { + this.bookmark = bookmark; + this.bookmark.setConversation(this); + } + + public void deregisterWithBookmark() { + if (this.bookmark != null) { + this.bookmark.setConversation(null); + } + } + + public Bookmark getBookmark() { + return this.bookmark; + } + + public boolean hasDuplicateMessage(Message message) { + synchronized (this.messages) { + for (int i = this.messages.size() - 1; i >= 0; --i) { + if (this.messages.get(i).similar(message)) { + return true; + } + } + } + return false; + } + + public Message findSentMessageWithBody(String body) { + synchronized (this.messages) { + for (int i = this.messages.size() - 1; i >= 0; --i) { + Message message = this.messages.get(i); + if (message.getStatus() == Message.STATUS_UNSEND || message.getStatus() == Message.STATUS_SEND) { + String otherBody; + if (message.hasFileOnRemoteHost()) { + otherBody = message.getFileParams().url.toString(); + } else { + otherBody = message.body; + } + if (otherBody != null && otherBody.equals(body)) { + return message; + } + } + } + return null; + } + } + + public long getLastMessageTransmitted() { + final long last_clear = getLastClearHistory(); + long last_received = 0; + synchronized (this.messages) { + for (int i = this.messages.size() - 1; i >= 0; --i) { + Message message = this.messages.get(i); + if (message.getStatus() == Message.STATUS_RECEIVED || message.isCarbon()) { + last_received = message.getTimeSent(); + break; + } + } + } + return Math.max(last_clear, last_received); + } + + public void setMutedTill(long value) { + this.setAttribute(ATTRIBUTE_MUTED_TILL, String.valueOf(value)); + } + + public boolean isMuted() { + return System.currentTimeMillis() < this.getLongAttribute(ATTRIBUTE_MUTED_TILL, 0); + } + + public boolean alwaysNotify() { + return mode == MODE_SINGLE || getBooleanAttribute(ATTRIBUTE_ALWAYS_NOTIFY, Config.ALWAYS_NOTIFY_BY_DEFAULT || isPnNA()); + } + + public boolean setAttribute(String key, String value) { + synchronized (this.attributes) { + try { + this.attributes.put(key, value); + return true; + } catch (JSONException e) { + return false; + } + } + } + + public boolean setAttribute(String key, List jids) { + JSONArray array = new JSONArray(); + for (Jid jid : jids) { + array.put(jid.toBareJid().toString()); + } + synchronized (this.attributes) { + try { + this.attributes.put(key, array); + return true; + } catch (JSONException e) { + e.printStackTrace(); + return false; + } + } + } + + public String getAttribute(String key) { + synchronized (this.attributes) { + try { + return this.attributes.getString(key); + } catch (JSONException e) { + return null; + } + } + } + + public List getJidListAttribute(String key) { + ArrayList list = new ArrayList<>(); + synchronized (this.attributes) { + try { + JSONArray array = this.attributes.getJSONArray(key); + for (int i = 0; i < array.length(); ++i) { + try { + list.add(Jid.fromString(array.getString(i))); + } catch (InvalidJidException e) { + //ignored + } + } + } catch (JSONException e) { + //ignored + } + } + return list; + } + + public int getIntAttribute(String key, int defaultValue) { + String value = this.getAttribute(key); + if (value == null) { + return defaultValue; + } else { + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + return defaultValue; + } + } + } + + public long getLongAttribute(String key, long defaultValue) { + String value = this.getAttribute(key); + if (value == null) { + return defaultValue; + } else { + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + return defaultValue; + } + } + } + + public boolean getBooleanAttribute(String key, boolean defaultValue) { + String value = this.getAttribute(key); + if (value == null) { + return defaultValue; + } else { + return Boolean.parseBoolean(value); + } + } + + public void add(Message message) { + message.setConversation(this); + synchronized (this.messages) { + this.messages.add(message); + } + } + + public void prepend(Message message) { + message.setConversation(this); + synchronized (this.messages) { + this.messages.add(0, message); + } + } + + public void addAll(int index, List messages) { + synchronized (this.messages) { + this.messages.addAll(index, messages); + } + account.getPgpDecryptionService().decrypt(messages); + } + + public void sort() { + synchronized (this.messages) { + Collections.sort(this.messages, new Comparator() { + @Override + public int compare(Message left, Message right) { + if (left.getTimeSent() < right.getTimeSent()) { + return -1; + } else if (left.getTimeSent() > right.getTimeSent()) { + return 1; + } else { + return 0; + } + } + }); + untieMessages(); + } + } + + private void untieMessages() { + for (Message message : this.messages) { + message.untie(); + } + } + + public int unreadCount() { + synchronized (this.messages) { + int count = 0; + for (int i = this.messages.size() - 1; i >= 0; --i) { + if (this.messages.get(i).isRead()) { + return count; + } + ++count; + } + return count; + } + } + + public class Smp { + public static final int STATUS_NONE = 0; + public static final int STATUS_CONTACT_REQUESTED = 1; + public static final int STATUS_WE_REQUESTED = 2; + public static final int STATUS_FAILED = 3; + public static final int STATUS_VERIFIED = 4; + + public String secret = null; + public String hint = null; + public int status = 0; + } } diff --git a/src/main/java/de/pixart/messenger/entities/DownloadableFile.java b/src/main/java/de/pixart/messenger/entities/DownloadableFile.java index 1a6e88881..ee0694196 100644 --- a/src/main/java/de/pixart/messenger/entities/DownloadableFile.java +++ b/src/main/java/de/pixart/messenger/entities/DownloadableFile.java @@ -6,78 +6,78 @@ import de.pixart.messenger.utils.MimeUtils; public class DownloadableFile extends File { - private static final long serialVersionUID = 2247012619505115863L; - - private long expectedSize = 0; - private String sha1sum; - private byte[] aeskey; - - private byte[] iv = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf }; - - public DownloadableFile(String path) { - super(path); - } - - public long getSize() { - return super.length(); - } - - public long getExpectedSize() { - return this.expectedSize; - } - - public String getMimeType() { - String path = this.getAbsolutePath(); - int start = path.lastIndexOf('.') + 1; - if (start < path.length()) { - String mime = MimeUtils.guessMimeTypeFromExtension(path.substring(start)); - return mime == null ? "" : mime; - } else { - return ""; - } - } - - public void setExpectedSize(long size) { - this.expectedSize = size; - } - - public String getSha1Sum() { - return this.sha1sum; - } - - public void setSha1Sum(String sum) { - this.sha1sum = sum; - } - - public void setKeyAndIv(byte[] keyIvCombo) { - if (keyIvCombo.length == 48) { - this.aeskey = new byte[32]; - this.iv = new byte[16]; - System.arraycopy(keyIvCombo, 0, this.iv, 0, 16); - System.arraycopy(keyIvCombo, 16, this.aeskey, 0, 32); - } else if (keyIvCombo.length >= 32) { - this.aeskey = new byte[32]; - System.arraycopy(keyIvCombo, 0, aeskey, 0, 32); - } else if (keyIvCombo.length >= 16) { - this.aeskey = new byte[16]; - System.arraycopy(keyIvCombo, 0, this.aeskey, 0, 16); - } - } - - public void setKey(byte[] key) { - this.aeskey = key; - } - - public void setIv(byte[] iv) { - this.iv = iv; - } - - public byte[] getKey() { - return this.aeskey; - } - - public byte[] getIv() { - return this.iv; - } + private static final long serialVersionUID = 2247012619505115863L; + + private long expectedSize = 0; + private String sha1sum; + private byte[] aeskey; + + private byte[] iv = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf}; + + public DownloadableFile(String path) { + super(path); + } + + public long getSize() { + return super.length(); + } + + public long getExpectedSize() { + return this.expectedSize; + } + + public String getMimeType() { + String path = this.getAbsolutePath(); + int start = path.lastIndexOf('.') + 1; + if (start < path.length()) { + String mime = MimeUtils.guessMimeTypeFromExtension(path.substring(start)); + return mime == null ? "" : mime; + } else { + return ""; + } + } + + public void setExpectedSize(long size) { + this.expectedSize = size; + } + + public String getSha1Sum() { + return this.sha1sum; + } + + public void setSha1Sum(String sum) { + this.sha1sum = sum; + } + + public void setKeyAndIv(byte[] keyIvCombo) { + if (keyIvCombo.length == 48) { + this.aeskey = new byte[32]; + this.iv = new byte[16]; + System.arraycopy(keyIvCombo, 0, this.iv, 0, 16); + System.arraycopy(keyIvCombo, 16, this.aeskey, 0, 32); + } else if (keyIvCombo.length >= 32) { + this.aeskey = new byte[32]; + System.arraycopy(keyIvCombo, 0, aeskey, 0, 32); + } else if (keyIvCombo.length >= 16) { + this.aeskey = new byte[16]; + System.arraycopy(keyIvCombo, 0, this.aeskey, 0, 16); + } + } + + public void setKey(byte[] key) { + this.aeskey = key; + } + + public void setIv(byte[] iv) { + this.iv = iv; + } + + public byte[] getKey() { + return this.aeskey; + } + + public byte[] getIv() { + return this.iv; + } } diff --git a/src/main/java/de/pixart/messenger/entities/ListItem.java b/src/main/java/de/pixart/messenger/entities/ListItem.java index cb0644499..7fbd37200 100644 --- a/src/main/java/de/pixart/messenger/entities/ListItem.java +++ b/src/main/java/de/pixart/messenger/entities/ListItem.java @@ -7,31 +7,31 @@ import java.util.List; import de.pixart.messenger.xmpp.jid.Jid; public interface ListItem extends Comparable { - String getDisplayName(); + String getDisplayName(); - String getDisplayJid(); + String getDisplayJid(); - Jid getJid(); + Jid getJid(); - List getTags(Context context); + List getTags(Context context); - final class Tag { - private final String name; - private final int color; + final class Tag { + private final String name; + private final int color; - public Tag(final String name, final int color) { - this.name = name; - this.color = color; - } + public Tag(final String name, final int color) { + this.name = name; + this.color = color; + } - public int getColor() { - return this.color; - } + public int getColor() { + return this.color; + } - public String getName() { - return this.name; - } - } + public String getName() { + return this.name; + } + } - boolean match(Context context, final String needle); + boolean match(Context context, final String needle); } diff --git a/src/main/java/de/pixart/messenger/entities/Message.java b/src/main/java/de/pixart/messenger/entities/Message.java index 9cdf57922..4224f6d6f 100644 --- a/src/main/java/de/pixart/messenger/entities/Message.java +++ b/src/main/java/de/pixart/messenger/entities/Message.java @@ -230,7 +230,7 @@ public class Message extends AbstractEntity { values.put(READ, read ? 1 : 0); values.put(EDITED, edited); values.put(OOB, oob ? 1 : 0); - values.put(ERROR_MESSAGE,errorMessage); + values.put(ERROR_MESSAGE, errorMessage); return values; } @@ -510,7 +510,8 @@ public class Message extends AbstractEntity { ); } - public static class MergeSeparator {} + public static class MergeSeparator { + } public SpannableStringBuilder getMergedBody() { SpannableStringBuilder body = new SpannableStringBuilder(this.body.trim()); diff --git a/src/main/java/de/pixart/messenger/entities/MucOptions.java b/src/main/java/de/pixart/messenger/entities/MucOptions.java index 4817f4bae..a95debd89 100644 --- a/src/main/java/de/pixart/messenger/entities/MucOptions.java +++ b/src/main/java/de/pixart/messenger/entities/MucOptions.java @@ -17,667 +17,667 @@ import de.pixart.messenger.xmpp.pep.Avatar; @SuppressLint("DefaultLocale") public class MucOptions { - private boolean mAutoPushConfiguration = true; - - public Account getAccount() { - return this.conversation.getAccount(); - } - - public void setSelf(User user) { - this.self = user; - } - - public void changeAffiliation(Jid jid, Affiliation affiliation) { - User user = findUserByRealJid(jid); - synchronized (users) { - if (user != null && user.getRole() == Role.NONE) { - users.remove(user); - if (affiliation.ranks(Affiliation.MEMBER)) { - user.affiliation = affiliation; - users.add(user); - } - } - } - } - - public void flagNoAutoPushConfiguration() { - mAutoPushConfiguration = false; - } - - public boolean autoPushConfiguration() { - return mAutoPushConfiguration; - } - - public enum Affiliation { - OWNER("owner", 4, R.string.owner), - ADMIN("admin", 3, R.string.admin), - MEMBER("member", 2, R.string.member), - OUTCAST("outcast", 0, R.string.outcast), - NONE("none", 1, R.string.no_affiliation); - - Affiliation(String string, int rank, int resId) { - this.string = string; - this.resId = resId; - this.rank = rank; - } - - private String string; - private int resId; - private int rank; - - public int getResId() { - return resId; - } - - @Override - public String toString() { - return this.string; - } - - public boolean outranks(Affiliation affiliation) { - return rank > affiliation.rank; - } - - public boolean ranks(Affiliation affiliation) { - return rank >= affiliation.rank; - } - } - - public enum Role { - MODERATOR("moderator", R.string.moderator,3), - VISITOR("visitor", R.string.visitor,1), - PARTICIPANT("participant", R.string.participant,2), - NONE("none", R.string.no_role,0); - - Role(String string, int resId, int rank) { - this.string = string; - this.resId = resId; - this.rank = rank; - } - - private String string; - private int resId; - private int rank; - - public int getResId() { - return resId; - } - - @Override - public String toString() { - return this.string; - } - - public boolean ranks(Role role) { - return rank >= role.rank; - } - } - - public enum Error { - NO_RESPONSE, - SERVER_NOT_FOUND, - NONE, - NICK_IN_USE, - PASSWORD_REQUIRED, - BANNED, - MEMBERS_ONLY, - KICKED, - SHUTDOWN, - UNKNOWN - } - - public static final String STATUS_CODE_SELF_PRESENCE = "110"; - public static final String STATUS_CODE_ROOM_CREATED = "201"; - public static final String STATUS_CODE_BANNED = "301"; - public static final String STATUS_CODE_CHANGED_NICK = "303"; - public static final String STATUS_CODE_KICKED = "307"; - public static final String STATUS_CODE_AFFILIATION_CHANGE = "321"; - public static final String STATUS_CODE_LOST_MEMBERSHIP = "322"; - public static final String STATUS_CODE_SHUTDOWN = "332"; - - private interface OnEventListener { - void onSuccess(); - - void onFailure(); - } - - public interface OnRenameListener extends OnEventListener { - - } - - public static class User implements Comparable { - private Role role = Role.NONE; - private Affiliation affiliation = Affiliation.NONE; - private Jid realJid; - private Jid fullJid; - private long pgpKeyId = 0; - private Avatar avatar; - private MucOptions options; - - public User(MucOptions options, Jid from) { - this.options = options; - this.fullJid = from; - } - - public String getName() { - return fullJid == null ? null : fullJid.getResourcepart(); - } - - public void setRealJid(Jid jid) { - this.realJid = jid != null ? jid.toBareJid() : null; - } - - public Role getRole() { - return this.role; - } - - public void setRole(String role) { - if (role == null) { - this.role = Role.NONE; - return; - } - role = role.toLowerCase(); - switch (role) { - case "moderator": - this.role = Role.MODERATOR; - break; - case "participant": - this.role = Role.PARTICIPANT; - break; - case "visitor": - this.role = Role.VISITOR; - break; - default: - this.role = Role.NONE; - break; - } - } - - public Affiliation getAffiliation() { - return this.affiliation; - } - - public void setAffiliation(String affiliation) { - if (affiliation == null) { - this.affiliation = Affiliation.NONE; - return; - } - affiliation = affiliation.toLowerCase(); - switch (affiliation) { - case "admin": - this.affiliation = Affiliation.ADMIN; - break; - case "owner": - this.affiliation = Affiliation.OWNER; - break; - case "member": - this.affiliation = Affiliation.MEMBER; - break; - case "outcast": - this.affiliation = Affiliation.OUTCAST; - break; - default: - this.affiliation = Affiliation.NONE; - } - } - - public void setPgpKeyId(long id) { - this.pgpKeyId = id; - } - - public long getPgpKeyId() { - return this.pgpKeyId; - } - - public Contact getContact() { - if (fullJid != null) { - return getAccount().getRoster().getContactFromRoster(realJid); - } else if (realJid != null){ - return getAccount().getRoster().getContact(realJid); - } else { - return null; - } - } - - public boolean setAvatar(Avatar avatar) { - if (this.avatar != null && this.avatar.equals(avatar)) { - return false; - } else { - this.avatar = avatar; - return true; - } - } - - public String getAvatar() { - return avatar == null ? null : avatar.getFilename(); - } - - public Account getAccount() { - return options.getAccount(); - } - - public Jid getFullJid() { - return fullJid; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - User user = (User) o; - - if (role != user.role) return false; - if (affiliation != user.affiliation) return false; - if (realJid != null ? !realJid.equals(user.realJid) : user.realJid != null) - return false; - return fullJid != null ? fullJid.equals(user.fullJid) : user.fullJid == null; - - } - - @Override - public int hashCode() { - int result = role != null ? role.hashCode() : 0; - result = 31 * result + (affiliation != null ? affiliation.hashCode() : 0); - result = 31 * result + (realJid != null ? realJid.hashCode() : 0); - result = 31 * result + (fullJid != null ? fullJid.hashCode() : 0); - return result; - } - - @Override - public String toString() { - return "[fulljid:"+String.valueOf(fullJid)+",realjid:"+String.valueOf(realJid)+",affiliation"+affiliation.toString()+"]"; - } - - public boolean realJidMatchesAccount() { - return realJid != null && realJid.equals(options.account.getJid().toBareJid()); - } - - @Override - public int compareTo(User another) { - if (another.getAffiliation().outranks(getAffiliation())) { - return 1; - } else if (getAffiliation().outranks(another.getAffiliation())) { - return -1; - } else { - return getComparableName().compareToIgnoreCase(another.getComparableName()); - } - } - - - private String getComparableName() { - Contact contact = getContact(); - if (contact != null) { - return contact.getDisplayName(); - } else { - String name = getName(); - return name == null ? "" : name; - } - } - - public Jid getRealJid() { - return realJid; - } - } - - private Account account; - private final Set users = new HashSet<>(); - private final List features = new ArrayList<>(); - private Data form = new Data(); - private Conversation conversation; - private boolean isOnline = false; - private Error error = Error.NONE; - public OnRenameListener onRenameListener = null; - private User self; - private String subject = null; - private String password = null; - public boolean mNickChangingInProgress = false; - - public MucOptions(Conversation conversation) { - this.account = conversation.getAccount(); - this.conversation = conversation; - this.self = new User(this,createJoinJid(getProposedNick())); - } - - public void updateFeatures(ArrayList features) { - this.features.clear(); - this.features.addAll(features); - } - - public void updateFormData(Data form) { - this.form = form; - } - - public boolean hasFeature(String feature) { - return this.features.contains(feature); - } - - public boolean canInvite() { - Field field = this.form.getFieldByName("muc#roomconfig_allowinvites"); - return !membersOnly() || self.getRole().ranks(Role.MODERATOR) || (field != null && "1".equals(field.getValue())); - } - - public boolean canChangeSubject() { - Field field = this.form.getFieldByName("muc#roomconfig_changesubject"); - return self.getRole().ranks(Role.MODERATOR) || (field != null && "1".equals(field.getValue())); - } - - public boolean participating() { - return !online() - || self.getRole().ranks(Role.PARTICIPANT) - || hasFeature("muc_unmoderated"); - } - - public boolean membersOnly() { - return hasFeature("muc_membersonly"); - } - - public boolean mamSupport() { - // Update with "urn:xmpp:mam:1" once we support it - return hasFeature("urn:xmpp:mam:0"); - } - - public boolean nonanonymous() { - return hasFeature("muc_nonanonymous"); - } - - public boolean persistent() { - return hasFeature("muc_persistent"); - } - - public boolean moderated() { - return hasFeature("muc_moderated"); - } - - public User deleteUser(Jid jid) { - User user = findUserByFullJid(jid); - if (user != null) { - synchronized (users) { - users.remove(user); - if (membersOnly() && - nonanonymous() && - user.affiliation.ranks(Affiliation.MEMBER) && - user.realJid != null) { - user.role = Role.NONE; - user.avatar = null; - user.fullJid = null; - users.add(user); - } - } - } - return user; - } - - public void updateUser(User user) { - User old; - if (user.fullJid == null && user.realJid != null) { - old = findUserByRealJid(user.realJid); - if (old != null) { - if (old.fullJid != null) { - return; //don't add. user already exists - } else { - synchronized (users) { - users.remove(old); - } - } - } - } else if (user.realJid != null) { - old = findUserByRealJid(user.realJid); - synchronized (users) { - if (old != null && old.fullJid == null) { - users.remove(old); - } - } - } - old = findUserByFullJid(user.getFullJid()); - synchronized (this.users) { - if (old != null) { - users.remove(old); - } + private boolean mAutoPushConfiguration = true; + + public Account getAccount() { + return this.conversation.getAccount(); + } + + public void setSelf(User user) { + this.self = user; + } + + public void changeAffiliation(Jid jid, Affiliation affiliation) { + User user = findUserByRealJid(jid); + synchronized (users) { + if (user != null && user.getRole() == Role.NONE) { + users.remove(user); + if (affiliation.ranks(Affiliation.MEMBER)) { + user.affiliation = affiliation; + users.add(user); + } + } + } + } + + public void flagNoAutoPushConfiguration() { + mAutoPushConfiguration = false; + } + + public boolean autoPushConfiguration() { + return mAutoPushConfiguration; + } + + public enum Affiliation { + OWNER("owner", 4, R.string.owner), + ADMIN("admin", 3, R.string.admin), + MEMBER("member", 2, R.string.member), + OUTCAST("outcast", 0, R.string.outcast), + NONE("none", 1, R.string.no_affiliation); + + Affiliation(String string, int rank, int resId) { + this.string = string; + this.resId = resId; + this.rank = rank; + } + + private String string; + private int resId; + private int rank; + + public int getResId() { + return resId; + } + + @Override + public String toString() { + return this.string; + } + + public boolean outranks(Affiliation affiliation) { + return rank > affiliation.rank; + } + + public boolean ranks(Affiliation affiliation) { + return rank >= affiliation.rank; + } + } + + public enum Role { + MODERATOR("moderator", R.string.moderator, 3), + VISITOR("visitor", R.string.visitor, 1), + PARTICIPANT("participant", R.string.participant, 2), + NONE("none", R.string.no_role, 0); + + Role(String string, int resId, int rank) { + this.string = string; + this.resId = resId; + this.rank = rank; + } + + private String string; + private int resId; + private int rank; + + public int getResId() { + return resId; + } + + @Override + public String toString() { + return this.string; + } + + public boolean ranks(Role role) { + return rank >= role.rank; + } + } + + public enum Error { + NO_RESPONSE, + SERVER_NOT_FOUND, + NONE, + NICK_IN_USE, + PASSWORD_REQUIRED, + BANNED, + MEMBERS_ONLY, + KICKED, + SHUTDOWN, + UNKNOWN + } + + public static final String STATUS_CODE_SELF_PRESENCE = "110"; + public static final String STATUS_CODE_ROOM_CREATED = "201"; + public static final String STATUS_CODE_BANNED = "301"; + public static final String STATUS_CODE_CHANGED_NICK = "303"; + public static final String STATUS_CODE_KICKED = "307"; + public static final String STATUS_CODE_AFFILIATION_CHANGE = "321"; + public static final String STATUS_CODE_LOST_MEMBERSHIP = "322"; + public static final String STATUS_CODE_SHUTDOWN = "332"; + + private interface OnEventListener { + void onSuccess(); + + void onFailure(); + } + + public interface OnRenameListener extends OnEventListener { + + } + + public static class User implements Comparable { + private Role role = Role.NONE; + private Affiliation affiliation = Affiliation.NONE; + private Jid realJid; + private Jid fullJid; + private long pgpKeyId = 0; + private Avatar avatar; + private MucOptions options; + + public User(MucOptions options, Jid from) { + this.options = options; + this.fullJid = from; + } + + public String getName() { + return fullJid == null ? null : fullJid.getResourcepart(); + } + + public void setRealJid(Jid jid) { + this.realJid = jid != null ? jid.toBareJid() : null; + } + + public Role getRole() { + return this.role; + } + + public void setRole(String role) { + if (role == null) { + this.role = Role.NONE; + return; + } + role = role.toLowerCase(); + switch (role) { + case "moderator": + this.role = Role.MODERATOR; + break; + case "participant": + this.role = Role.PARTICIPANT; + break; + case "visitor": + this.role = Role.VISITOR; + break; + default: + this.role = Role.NONE; + break; + } + } + + public Affiliation getAffiliation() { + return this.affiliation; + } + + public void setAffiliation(String affiliation) { + if (affiliation == null) { + this.affiliation = Affiliation.NONE; + return; + } + affiliation = affiliation.toLowerCase(); + switch (affiliation) { + case "admin": + this.affiliation = Affiliation.ADMIN; + break; + case "owner": + this.affiliation = Affiliation.OWNER; + break; + case "member": + this.affiliation = Affiliation.MEMBER; + break; + case "outcast": + this.affiliation = Affiliation.OUTCAST; + break; + default: + this.affiliation = Affiliation.NONE; + } + } + + public void setPgpKeyId(long id) { + this.pgpKeyId = id; + } + + public long getPgpKeyId() { + return this.pgpKeyId; + } + + public Contact getContact() { + if (fullJid != null) { + return getAccount().getRoster().getContactFromRoster(realJid); + } else if (realJid != null) { + return getAccount().getRoster().getContact(realJid); + } else { + return null; + } + } + + public boolean setAvatar(Avatar avatar) { + if (this.avatar != null && this.avatar.equals(avatar)) { + return false; + } else { + this.avatar = avatar; + return true; + } + } + + public String getAvatar() { + return avatar == null ? null : avatar.getFilename(); + } + + public Account getAccount() { + return options.getAccount(); + } + + public Jid getFullJid() { + return fullJid; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + User user = (User) o; + + if (role != user.role) return false; + if (affiliation != user.affiliation) return false; + if (realJid != null ? !realJid.equals(user.realJid) : user.realJid != null) + return false; + return fullJid != null ? fullJid.equals(user.fullJid) : user.fullJid == null; + + } + + @Override + public int hashCode() { + int result = role != null ? role.hashCode() : 0; + result = 31 * result + (affiliation != null ? affiliation.hashCode() : 0); + result = 31 * result + (realJid != null ? realJid.hashCode() : 0); + result = 31 * result + (fullJid != null ? fullJid.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "[fulljid:" + String.valueOf(fullJid) + ",realjid:" + String.valueOf(realJid) + ",affiliation" + affiliation.toString() + "]"; + } + + public boolean realJidMatchesAccount() { + return realJid != null && realJid.equals(options.account.getJid().toBareJid()); + } + + @Override + public int compareTo(User another) { + if (another.getAffiliation().outranks(getAffiliation())) { + return 1; + } else if (getAffiliation().outranks(another.getAffiliation())) { + return -1; + } else { + return getComparableName().compareToIgnoreCase(another.getComparableName()); + } + } + + + private String getComparableName() { + Contact contact = getContact(); + if (contact != null) { + return contact.getDisplayName(); + } else { + String name = getName(); + return name == null ? "" : name; + } + } + + public Jid getRealJid() { + return realJid; + } + } + + private Account account; + private final Set users = new HashSet<>(); + private final List features = new ArrayList<>(); + private Data form = new Data(); + private Conversation conversation; + private boolean isOnline = false; + private Error error = Error.NONE; + public OnRenameListener onRenameListener = null; + private User self; + private String subject = null; + private String password = null; + public boolean mNickChangingInProgress = false; + + public MucOptions(Conversation conversation) { + this.account = conversation.getAccount(); + this.conversation = conversation; + this.self = new User(this, createJoinJid(getProposedNick())); + } + + public void updateFeatures(ArrayList features) { + this.features.clear(); + this.features.addAll(features); + } + + public void updateFormData(Data form) { + this.form = form; + } + + public boolean hasFeature(String feature) { + return this.features.contains(feature); + } + + public boolean canInvite() { + Field field = this.form.getFieldByName("muc#roomconfig_allowinvites"); + return !membersOnly() || self.getRole().ranks(Role.MODERATOR) || (field != null && "1".equals(field.getValue())); + } + + public boolean canChangeSubject() { + Field field = this.form.getFieldByName("muc#roomconfig_changesubject"); + return self.getRole().ranks(Role.MODERATOR) || (field != null && "1".equals(field.getValue())); + } + + public boolean participating() { + return !online() + || self.getRole().ranks(Role.PARTICIPANT) + || hasFeature("muc_unmoderated"); + } + + public boolean membersOnly() { + return hasFeature("muc_membersonly"); + } + + public boolean mamSupport() { + // Update with "urn:xmpp:mam:1" once we support it + return hasFeature("urn:xmpp:mam:0"); + } + + public boolean nonanonymous() { + return hasFeature("muc_nonanonymous"); + } + + public boolean persistent() { + return hasFeature("muc_persistent"); + } + + public boolean moderated() { + return hasFeature("muc_moderated"); + } + + public User deleteUser(Jid jid) { + User user = findUserByFullJid(jid); + if (user != null) { + synchronized (users) { + users.remove(user); + if (membersOnly() && + nonanonymous() && + user.affiliation.ranks(Affiliation.MEMBER) && + user.realJid != null) { + user.role = Role.NONE; + user.avatar = null; + user.fullJid = null; + users.add(user); + } + } + } + return user; + } + + public void updateUser(User user) { + User old; + if (user.fullJid == null && user.realJid != null) { + old = findUserByRealJid(user.realJid); + if (old != null) { + if (old.fullJid != null) { + return; //don't add. user already exists + } else { + synchronized (users) { + users.remove(old); + } + } + } + } else if (user.realJid != null) { + old = findUserByRealJid(user.realJid); + synchronized (users) { + if (old != null && old.fullJid == null) { + users.remove(old); + } + } + } + old = findUserByFullJid(user.getFullJid()); + synchronized (this.users) { + if (old != null) { + users.remove(old); + } if ((!membersOnly() || user.getAffiliation().ranks(Affiliation.MEMBER)) - && user.getAffiliation().outranks(Affiliation.OUTCAST)){ + && user.getAffiliation().outranks(Affiliation.OUTCAST)) { this.users.add(user); } - } - } - - public User findUserByFullJid(Jid jid) { - if (jid == null) { - return null; - } - synchronized (users) { - for (User user : users) { - if (jid.equals(user.getFullJid())) { - return user; - } - } - } - return null; - } - - private User findUserByRealJid(Jid jid) { - if (jid == null) { - return null; - } - synchronized (users) { - for (User user : users) { - if (jid.equals(user.realJid)) { - return user; - } - } - } - return null; - } - - public boolean isContactInRoom(Contact contact) { - return findUserByRealJid(contact.getJid().toBareJid()) != null; - } - - public boolean isUserInRoom(Jid jid) { - return findUserByFullJid(jid) != null; - } - - public void setError(Error error) { - this.isOnline = isOnline && error == Error.NONE; - this.error = error; - } - - public void setOnline() { - this.isOnline = true; - } - - public ArrayList getUsers() { - return getUsers(true); - } - - public ArrayList getUsers(boolean includeOffline) { - synchronized (users) { - if (includeOffline) { - return new ArrayList<>(users); - } else { - ArrayList onlineUsers = new ArrayList<>(); - for (User user : users) { - if (user.getRole().ranks(Role.PARTICIPANT)) { - onlineUsers.add(user); - } - } - return onlineUsers; - } - } - } - - public List getUsers(int max) { - ArrayList users = getUsers(); - return users.subList(0, Math.min(max, users.size())); - } - - public int getUserCount() { - synchronized (users) { - return users.size(); - } - } - - public String getProposedNick() { - if (conversation.getBookmark() != null - && conversation.getBookmark().getNick() != null - && !conversation.getBookmark().getNick().trim().isEmpty()) { - return conversation.getBookmark().getNick().trim(); - } else if (!conversation.getJid().isBareJid()) { - return conversation.getJid().getResourcepart(); - } else { - return account.getUsername(); - } - } - - public String getActualNick() { - if (this.self.getName() != null) { - return this.self.getName(); - } else { - return this.getProposedNick(); - } - } - - public boolean online() { - return this.isOnline; - } - - public Error getError() { - return this.error; - } - - public void setOnRenameListener(OnRenameListener listener) { - this.onRenameListener = listener; - } - - public void setOffline() { - synchronized (users) { - this.users.clear(); - } - this.error = Error.NO_RESPONSE; - this.isOnline = false; - } - - public User getSelf() { - return self; - } - - public void setSubject(String content) { - this.subject = content; - } - - public String getSubject() { - return this.subject; - } - - public String createNameFromParticipants() { - if (users.size() >= 1) { - List names = new ArrayList<>(); - for (User user : getUsers(5)) { - Contact contact = user.getContact(); - if (contact != null && !contact.getDisplayName().isEmpty()) { - names.add(contact.getDisplayName().split("\\s+")[0]); - } else if (user.getName() != null){ - names.add(user.getName()); - } - } - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < names.size(); ++i) { - builder.append(names.get(i)); - if (i != names.size() - 1) { - builder.append(", "); - } - } - return builder.toString(); - } else { - return null; - } - } - - public long[] getPgpKeyIds() { - List ids = new ArrayList<>(); - for (User user : this.users) { - if (user.getPgpKeyId() != 0) { - ids.add(user.getPgpKeyId()); - } - } - ids.add(account.getPgpId()); - long[] primitiveLongArray = new long[ids.size()]; - for (int i = 0; i < ids.size(); ++i) { - primitiveLongArray[i] = ids.get(i); - } - return primitiveLongArray; - } - - public boolean pgpKeysInUse() { - synchronized (users) { - for (User user : users) { - if (user.getPgpKeyId() != 0) { - return true; - } - } - } - return false; - } - - public boolean everybodyHasKeys() { - synchronized (users) { - for (User user : users) { - if (user.getPgpKeyId() == 0) { - return false; - } - } - } - return true; - } - - public Jid createJoinJid(String nick) { - try { - return Jid.fromString(this.conversation.getJid().toBareJid().toString() + "/" + nick); - } catch (final InvalidJidException e) { - return null; - } - } - - public Jid getTrueCounterpart(Jid jid) { - if (jid.equals(getSelf().getFullJid())) { - return account.getJid().toBareJid(); - } - User user = findUserByFullJid(jid); - return user == null ? null : user.realJid; - } - - public String getPassword() { - this.password = conversation.getAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD); - if (this.password == null && conversation.getBookmark() != null - && conversation.getBookmark().getPassword() != null) { - return conversation.getBookmark().getPassword(); - } else { - return this.password; - } - } - - public void setPassword(String password) { - if (conversation.getBookmark() != null) { - conversation.getBookmark().setPassword(password); - } else { - this.password = password; - } - conversation.setAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD, password); - } - - public Conversation getConversation() { - return this.conversation; - } - - public List getMembers() { - ArrayList members = new ArrayList<>(); - synchronized (users) { - for (User user : users) { - if (user.affiliation.ranks(Affiliation.MEMBER) && user.realJid != null) { - members.add(user.realJid); - } - } - } - return members; - } + } + } + + public User findUserByFullJid(Jid jid) { + if (jid == null) { + return null; + } + synchronized (users) { + for (User user : users) { + if (jid.equals(user.getFullJid())) { + return user; + } + } + } + return null; + } + + private User findUserByRealJid(Jid jid) { + if (jid == null) { + return null; + } + synchronized (users) { + for (User user : users) { + if (jid.equals(user.realJid)) { + return user; + } + } + } + return null; + } + + public boolean isContactInRoom(Contact contact) { + return findUserByRealJid(contact.getJid().toBareJid()) != null; + } + + public boolean isUserInRoom(Jid jid) { + return findUserByFullJid(jid) != null; + } + + public void setError(Error error) { + this.isOnline = isOnline && error == Error.NONE; + this.error = error; + } + + public void setOnline() { + this.isOnline = true; + } + + public ArrayList getUsers() { + return getUsers(true); + } + + public ArrayList getUsers(boolean includeOffline) { + synchronized (users) { + if (includeOffline) { + return new ArrayList<>(users); + } else { + ArrayList onlineUsers = new ArrayList<>(); + for (User user : users) { + if (user.getRole().ranks(Role.PARTICIPANT)) { + onlineUsers.add(user); + } + } + return onlineUsers; + } + } + } + + public List getUsers(int max) { + ArrayList users = getUsers(); + return users.subList(0, Math.min(max, users.size())); + } + + public int getUserCount() { + synchronized (users) { + return users.size(); + } + } + + public String getProposedNick() { + if (conversation.getBookmark() != null + && conversation.getBookmark().getNick() != null + && !conversation.getBookmark().getNick().trim().isEmpty()) { + return conversation.getBookmark().getNick().trim(); + } else if (!conversation.getJid().isBareJid()) { + return conversation.getJid().getResourcepart(); + } else { + return account.getUsername(); + } + } + + public String getActualNick() { + if (this.self.getName() != null) { + return this.self.getName(); + } else { + return this.getProposedNick(); + } + } + + public boolean online() { + return this.isOnline; + } + + public Error getError() { + return this.error; + } + + public void setOnRenameListener(OnRenameListener listener) { + this.onRenameListener = listener; + } + + public void setOffline() { + synchronized (users) { + this.users.clear(); + } + this.error = Error.NO_RESPONSE; + this.isOnline = false; + } + + public User getSelf() { + return self; + } + + public void setSubject(String content) { + this.subject = content; + } + + public String getSubject() { + return this.subject; + } + + public String createNameFromParticipants() { + if (users.size() >= 1) { + List names = new ArrayList<>(); + for (User user : getUsers(5)) { + Contact contact = user.getContact(); + if (contact != null && !contact.getDisplayName().isEmpty()) { + names.add(contact.getDisplayName().split("\\s+")[0]); + } else if (user.getName() != null) { + names.add(user.getName()); + } + } + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < names.size(); ++i) { + builder.append(names.get(i)); + if (i != names.size() - 1) { + builder.append(", "); + } + } + return builder.toString(); + } else { + return null; + } + } + + public long[] getPgpKeyIds() { + List ids = new ArrayList<>(); + for (User user : this.users) { + if (user.getPgpKeyId() != 0) { + ids.add(user.getPgpKeyId()); + } + } + ids.add(account.getPgpId()); + long[] primitiveLongArray = new long[ids.size()]; + for (int i = 0; i < ids.size(); ++i) { + primitiveLongArray[i] = ids.get(i); + } + return primitiveLongArray; + } + + public boolean pgpKeysInUse() { + synchronized (users) { + for (User user : users) { + if (user.getPgpKeyId() != 0) { + return true; + } + } + } + return false; + } + + public boolean everybodyHasKeys() { + synchronized (users) { + for (User user : users) { + if (user.getPgpKeyId() == 0) { + return false; + } + } + } + return true; + } + + public Jid createJoinJid(String nick) { + try { + return Jid.fromString(this.conversation.getJid().toBareJid().toString() + "/" + nick); + } catch (final InvalidJidException e) { + return null; + } + } + + public Jid getTrueCounterpart(Jid jid) { + if (jid.equals(getSelf().getFullJid())) { + return account.getJid().toBareJid(); + } + User user = findUserByFullJid(jid); + return user == null ? null : user.realJid; + } + + public String getPassword() { + this.password = conversation.getAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD); + if (this.password == null && conversation.getBookmark() != null + && conversation.getBookmark().getPassword() != null) { + return conversation.getBookmark().getPassword(); + } else { + return this.password; + } + } + + public void setPassword(String password) { + if (conversation.getBookmark() != null) { + conversation.getBookmark().setPassword(password); + } else { + this.password = password; + } + conversation.setAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD, password); + } + + public Conversation getConversation() { + return this.conversation; + } + + public List getMembers() { + ArrayList members = new ArrayList<>(); + synchronized (users) { + for (User user : users) { + if (user.affiliation.ranks(Affiliation.MEMBER) && user.realJid != null) { + members.add(user.realJid); + } + } + } + return members; + } } diff --git a/src/main/java/de/pixart/messenger/entities/Presence.java b/src/main/java/de/pixart/messenger/entities/Presence.java index fe7bac6ee..af467f1f6 100644 --- a/src/main/java/de/pixart/messenger/entities/Presence.java +++ b/src/main/java/de/pixart/messenger/entities/Presence.java @@ -6,87 +6,91 @@ import de.pixart.messenger.xml.Element; public class Presence implements Comparable { - public enum Status { - CHAT, ONLINE, AWAY, XA, DND, OFFLINE; - - public String toShowString() { - switch(this) { - case CHAT: return "chat"; - case AWAY: return "away"; - case XA: return "xa"; - case DND: return "dnd"; - } - return null; - } - - public static Status fromShowString(String show) { - if (show == null) { - return ONLINE; - } else { - switch (show.toLowerCase(Locale.US)) { - case "away": - return AWAY; - case "xa": - return XA; - case "dnd": - return DND; - case "chat": - return CHAT; - default: - return ONLINE; - } - } - } - } - - private final Status status; - private ServiceDiscoveryResult disco; - private final String ver; - private final String hash; - private final String message; - - private Presence(Status status, String ver, String hash, String message) { - this.status = status; - this.ver = ver; - this.hash = hash; - this.message = message; - } - - public static Presence parse(String show, Element caps, String message) { - final String hash = caps == null ? null : caps.getAttribute("hash"); - final String ver = caps == null ? null : caps.getAttribute("ver"); - return new Presence(Status.fromShowString(show), ver, hash, message); - } - - public int compareTo(Object other) { - return this.status.compareTo(((Presence)other).status); - } - - public Status getStatus() { - return this.status; - } - - public boolean hasCaps() { - return ver != null && hash != null; - } - - public String getVer() { - return this.ver; - } - - public String getHash() { - return this.hash; - } - - public String getMessage() { - return this.message; - } - - public void setServiceDiscoveryResult(ServiceDiscoveryResult disco) { - this.disco = disco; - } - - public ServiceDiscoveryResult getServiceDiscoveryResult() { - return disco; - } + public enum Status { + CHAT, ONLINE, AWAY, XA, DND, OFFLINE; + + public String toShowString() { + switch (this) { + case CHAT: + return "chat"; + case AWAY: + return "away"; + case XA: + return "xa"; + case DND: + return "dnd"; + } + return null; + } + + public static Status fromShowString(String show) { + if (show == null) { + return ONLINE; + } else { + switch (show.toLowerCase(Locale.US)) { + case "away": + return AWAY; + case "xa": + return XA; + case "dnd": + return DND; + case "chat": + return CHAT; + default: + return ONLINE; + } + } + } + } + + private final Status status; + private ServiceDiscoveryResult disco; + private final String ver; + private final String hash; + private final String message; + + private Presence(Status status, String ver, String hash, String message) { + this.status = status; + this.ver = ver; + this.hash = hash; + this.message = message; + } + + public static Presence parse(String show, Element caps, String message) { + final String hash = caps == null ? null : caps.getAttribute("hash"); + final String ver = caps == null ? null : caps.getAttribute("ver"); + return new Presence(Status.fromShowString(show), ver, hash, message); + } + + public int compareTo(Object other) { + return this.status.compareTo(((Presence) other).status); + } + + public Status getStatus() { + return this.status; + } + + public boolean hasCaps() { + return ver != null && hash != null; + } + + public String getVer() { + return this.ver; + } + + public String getHash() { + return this.hash; + } + + public String getMessage() { + return this.message; + } + + public void setServiceDiscoveryResult(ServiceDiscoveryResult disco) { + this.disco = disco; + } + + public ServiceDiscoveryResult getServiceDiscoveryResult() { + return disco; + } } diff --git a/src/main/java/de/pixart/messenger/entities/PresenceTemplate.java b/src/main/java/de/pixart/messenger/entities/PresenceTemplate.java index b8bd167db..ab71a41fd 100644 --- a/src/main/java/de/pixart/messenger/entities/PresenceTemplate.java +++ b/src/main/java/de/pixart/messenger/entities/PresenceTemplate.java @@ -6,71 +6,71 @@ import android.database.Cursor; public class PresenceTemplate extends AbstractEntity { - public static final String TABELNAME = "presence_templates"; - public static final String LAST_USED = "last_used"; - public static final String MESSAGE = "message"; - public static final String STATUS = "status"; - - private long lastUsed = 0; - private String statusMessage; - private Presence.Status status = Presence.Status.ONLINE; - - public PresenceTemplate(Presence.Status status, String statusMessage) { - this.status = status; - this.statusMessage = statusMessage; - this.lastUsed = System.currentTimeMillis(); - this.uuid = java.util.UUID.randomUUID().toString(); - } - - private PresenceTemplate() { - - } - - @Override - public ContentValues getContentValues() { - final String show = status.toShowString(); - ContentValues values = new ContentValues(); - values.put(LAST_USED, lastUsed); - values.put(MESSAGE, statusMessage); - values.put(STATUS, show == null ? "" : show); - values.put(UUID, uuid); - return values; - } - - public static PresenceTemplate fromCursor(Cursor cursor) { - PresenceTemplate template = new PresenceTemplate(); - template.uuid = cursor.getString(cursor.getColumnIndex(UUID)); - template.lastUsed = cursor.getLong(cursor.getColumnIndex(LAST_USED)); - template.statusMessage = cursor.getString(cursor.getColumnIndex(MESSAGE)); - template.status = Presence.Status.fromShowString(cursor.getString(cursor.getColumnIndex(STATUS))); - return template; - } - - public Presence.Status getStatus() { - return status; - } - - public String getStatusMessage() { - return statusMessage; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - PresenceTemplate template = (PresenceTemplate) o; - - if (statusMessage != null ? !statusMessage.equals(template.statusMessage) : template.statusMessage != null) - return false; - return status == template.status; - - } - - @Override - public int hashCode() { - int result = statusMessage != null ? statusMessage.hashCode() : 0; - result = 31 * result + status.hashCode(); - return result; - } + public static final String TABELNAME = "presence_templates"; + public static final String LAST_USED = "last_used"; + public static final String MESSAGE = "message"; + public static final String STATUS = "status"; + + private long lastUsed = 0; + private String statusMessage; + private Presence.Status status = Presence.Status.ONLINE; + + public PresenceTemplate(Presence.Status status, String statusMessage) { + this.status = status; + this.statusMessage = statusMessage; + this.lastUsed = System.currentTimeMillis(); + this.uuid = java.util.UUID.randomUUID().toString(); + } + + private PresenceTemplate() { + + } + + @Override + public ContentValues getContentValues() { + final String show = status.toShowString(); + ContentValues values = new ContentValues(); + values.put(LAST_USED, lastUsed); + values.put(MESSAGE, statusMessage); + values.put(STATUS, show == null ? "" : show); + values.put(UUID, uuid); + return values; + } + + public static PresenceTemplate fromCursor(Cursor cursor) { + PresenceTemplate template = new PresenceTemplate(); + template.uuid = cursor.getString(cursor.getColumnIndex(UUID)); + template.lastUsed = cursor.getLong(cursor.getColumnIndex(LAST_USED)); + template.statusMessage = cursor.getString(cursor.getColumnIndex(MESSAGE)); + template.status = Presence.Status.fromShowString(cursor.getString(cursor.getColumnIndex(STATUS))); + return template; + } + + public Presence.Status getStatus() { + return status; + } + + public String getStatusMessage() { + return statusMessage; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + PresenceTemplate template = (PresenceTemplate) o; + + if (statusMessage != null ? !statusMessage.equals(template.statusMessage) : template.statusMessage != null) + return false; + return status == template.status; + + } + + @Override + public int hashCode() { + int result = statusMessage != null ? statusMessage.hashCode() : 0; + result = 31 * result + status.hashCode(); + return result; + } } diff --git a/src/main/java/de/pixart/messenger/entities/Presences.java b/src/main/java/de/pixart/messenger/entities/Presences.java index 98ea58fcf..6afd973fc 100644 --- a/src/main/java/de/pixart/messenger/entities/Presences.java +++ b/src/main/java/de/pixart/messenger/entities/Presences.java @@ -9,122 +9,122 @@ import java.util.List; import java.util.Map; public class Presences { - private final Hashtable presences = new Hashtable<>(); + private final Hashtable presences = new Hashtable<>(); - public Hashtable getPresences() { - return this.presences; - } + public Hashtable getPresences() { + return this.presences; + } - public void updatePresence(String resource, Presence presence) { - synchronized (this.presences) { - this.presences.put(resource, presence); - } - } + public void updatePresence(String resource, Presence presence) { + synchronized (this.presences) { + this.presences.put(resource, presence); + } + } - public void removePresence(String resource) { - synchronized (this.presences) { - this.presences.remove(resource); - } - } + public void removePresence(String resource) { + synchronized (this.presences) { + this.presences.remove(resource); + } + } - public void clearPresences() { - synchronized (this.presences) { - this.presences.clear(); - } - } + public void clearPresences() { + synchronized (this.presences) { + this.presences.clear(); + } + } - public Presence.Status getShownStatus() { - Presence.Status status = Presence.Status.OFFLINE; - synchronized (this.presences) { - for(Presence p : presences.values()) { - if (p.getStatus() == Presence.Status.DND) { - return p.getStatus(); - } else if (p.getStatus().compareTo(status) < 0){ - status = p.getStatus(); - } - } - } - return status; - } + public Presence.Status getShownStatus() { + Presence.Status status = Presence.Status.OFFLINE; + synchronized (this.presences) { + for (Presence p : presences.values()) { + if (p.getStatus() == Presence.Status.DND) { + return p.getStatus(); + } else if (p.getStatus().compareTo(status) < 0) { + status = p.getStatus(); + } + } + } + return status; + } - public int size() { - synchronized (this.presences) { - return presences.size(); - } - } + public int size() { + synchronized (this.presences) { + return presences.size(); + } + } - public String[] toResourceArray() { - synchronized (this.presences) { - final String[] presencesArray = new String[presences.size()]; - presences.keySet().toArray(presencesArray); - return presencesArray; - } - } + public String[] toResourceArray() { + synchronized (this.presences) { + final String[] presencesArray = new String[presences.size()]; + presences.keySet().toArray(presencesArray); + return presencesArray; + } + } - public List asTemplates() { - synchronized (this.presences) { - ArrayList templates = new ArrayList<>(presences.size()); - for(Presence p : presences.values()) { - if (p.getMessage() != null && !p.getMessage().trim().isEmpty()) { - templates.add(new PresenceTemplate(p.getStatus(), p.getMessage())); - } - } - return templates; - } - } + public List asTemplates() { + synchronized (this.presences) { + ArrayList templates = new ArrayList<>(presences.size()); + for (Presence p : presences.values()) { + if (p.getMessage() != null && !p.getMessage().trim().isEmpty()) { + templates.add(new PresenceTemplate(p.getStatus(), p.getMessage())); + } + } + return templates; + } + } - public boolean has(String presence) { - synchronized (this.presences) { - return presences.containsKey(presence); - } - } + public boolean has(String presence) { + synchronized (this.presences) { + return presences.containsKey(presence); + } + } - public List getStatusMessages() { - ArrayList messages = new ArrayList<>(); - synchronized (this.presences) { - for(Presence presence : this.presences.values()) { - String message = presence.getMessage() == null ? null : presence.getMessage().trim(); - if (message != null && !message.isEmpty() && !messages.contains(message)) { - messages.add(message); - } - } - } - return messages; - } + public List getStatusMessages() { + ArrayList messages = new ArrayList<>(); + synchronized (this.presences) { + for (Presence presence : this.presences.values()) { + String message = presence.getMessage() == null ? null : presence.getMessage().trim(); + if (message != null && !message.isEmpty() && !messages.contains(message)) { + messages.add(message); + } + } + } + return messages; + } - public boolean allOrNonSupport(String namespace) { - synchronized (this.presences) { - for(Presence presence : this.presences.values()) { - ServiceDiscoveryResult disco = presence.getServiceDiscoveryResult(); - if (disco == null || !disco.getFeatures().contains(namespace)) { - return false; - } - } - } - return true; - } + public boolean allOrNonSupport(String namespace) { + synchronized (this.presences) { + for (Presence presence : this.presences.values()) { + ServiceDiscoveryResult disco = presence.getServiceDiscoveryResult(); + if (disco == null || !disco.getFeatures().contains(namespace)) { + return false; + } + } + } + return true; + } - public Pair,Map> toTypeAndNameMap() { - Map typeMap = new HashMap<>(); - Map nameMap = new HashMap<>(); - synchronized (this.presences) { - for(Map.Entry presenceEntry : this.presences.entrySet()) { - String resource = presenceEntry.getKey(); - Presence presence = presenceEntry.getValue(); - ServiceDiscoveryResult serviceDiscoveryResult = presence == null ? null : presence.getServiceDiscoveryResult(); - if (serviceDiscoveryResult != null && serviceDiscoveryResult.getIdentities().size() > 0) { - ServiceDiscoveryResult.Identity identity = serviceDiscoveryResult.getIdentities().get(0); - String type = identity.getType(); - String name = identity.getName(); - if (type != null) { - typeMap.put(resource,type); - } - if (name != null) { - nameMap.put(resource, name); - } - } - } - } - return new Pair(typeMap,nameMap); - } + public Pair, Map> toTypeAndNameMap() { + Map typeMap = new HashMap<>(); + Map nameMap = new HashMap<>(); + synchronized (this.presences) { + for (Map.Entry presenceEntry : this.presences.entrySet()) { + String resource = presenceEntry.getKey(); + Presence presence = presenceEntry.getValue(); + ServiceDiscoveryResult serviceDiscoveryResult = presence == null ? null : presence.getServiceDiscoveryResult(); + if (serviceDiscoveryResult != null && serviceDiscoveryResult.getIdentities().size() > 0) { + ServiceDiscoveryResult.Identity identity = serviceDiscoveryResult.getIdentities().get(0); + String type = identity.getType(); + String name = identity.getName(); + if (type != null) { + typeMap.put(resource, type); + } + if (name != null) { + nameMap.put(resource, name); + } + } + } + } + return new Pair(typeMap, nameMap); + } } diff --git a/src/main/java/de/pixart/messenger/entities/Roster.java b/src/main/java/de/pixart/messenger/entities/Roster.java index e267142bc..bdb7265f3 100644 --- a/src/main/java/de/pixart/messenger/entities/Roster.java +++ b/src/main/java/de/pixart/messenger/entities/Roster.java @@ -8,89 +8,89 @@ import java.util.List; import de.pixart.messenger.xmpp.jid.Jid; public class Roster { - final Account account; - final HashMap contacts = new HashMap<>(); - private String version = null; + final Account account; + final HashMap contacts = new HashMap<>(); + private String version = null; - public Roster(Account account) { - this.account = account; - } + public Roster(Account account) { + this.account = account; + } - public Contact getContactFromRoster(Jid jid) { - if (jid == null) { - return null; - } - synchronized (this.contacts) { - Contact contact = contacts.get(jid.toBareJid()); - if (contact != null && contact.showInRoster()) { - return contact; - } else { - return null; - } - } - } + public Contact getContactFromRoster(Jid jid) { + if (jid == null) { + return null; + } + synchronized (this.contacts) { + Contact contact = contacts.get(jid.toBareJid()); + if (contact != null && contact.showInRoster()) { + return contact; + } else { + return null; + } + } + } - public Contact getContact(final Jid jid) { - synchronized (this.contacts) { - if (!contacts.containsKey(jid.toBareJid())) { - Contact contact = new Contact(jid.toBareJid()); - contact.setAccount(account); - contacts.put(contact.getJid().toBareJid(), contact); - return contact; - } - return contacts.get(jid.toBareJid()); - } - } + public Contact getContact(final Jid jid) { + synchronized (this.contacts) { + if (!contacts.containsKey(jid.toBareJid())) { + Contact contact = new Contact(jid.toBareJid()); + contact.setAccount(account); + contacts.put(contact.getJid().toBareJid(), contact); + return contact; + } + return contacts.get(jid.toBareJid()); + } + } - public void clearPresences() { - for (Contact contact : getContacts()) { - contact.clearPresences(); - } - } + public void clearPresences() { + for (Contact contact : getContacts()) { + contact.clearPresences(); + } + } - public void markAllAsNotInRoster() { - for (Contact contact : getContacts()) { - contact.resetOption(Contact.Options.IN_ROSTER); - } - } + public void markAllAsNotInRoster() { + for (Contact contact : getContacts()) { + contact.resetOption(Contact.Options.IN_ROSTER); + } + } - public List getWithSystemAccounts() { - List with = getContacts(); - for(Iterator iterator = with.iterator(); iterator.hasNext();) { - Contact contact = iterator.next(); - if (contact.getSystemAccount() == null) { - iterator.remove(); - } - } - return with; - } + public List getWithSystemAccounts() { + List with = getContacts(); + for (Iterator iterator = with.iterator(); iterator.hasNext(); ) { + Contact contact = iterator.next(); + if (contact.getSystemAccount() == null) { + iterator.remove(); + } + } + return with; + } - public List getContacts() { - synchronized (this.contacts) { - return new ArrayList<>(this.contacts.values()); - } - } + public List getContacts() { + synchronized (this.contacts) { + return new ArrayList<>(this.contacts.values()); + } + } - public void initContact(final Contact contact) { - if (contact == null) { - return; - } - contact.setAccount(account); - contact.setOption(Contact.Options.IN_ROSTER); - synchronized (this.contacts) { - contacts.put(contact.getJid().toBareJid(), contact); - } - } + public void initContact(final Contact contact) { + if (contact == null) { + return; + } + contact.setAccount(account); + contact.setOption(Contact.Options.IN_ROSTER); + synchronized (this.contacts) { + contacts.put(contact.getJid().toBareJid(), contact); + } + } - public void setVersion(String version) { - this.version = version; - } + public void setVersion(String version) { + this.version = version; + } - public String getVersion() { - return this.version; - } + public String getVersion() { + return this.version; + } - public Account getAccount() { - return this.account; - } + public Account getAccount() { + return this.account; + } } diff --git a/src/main/java/de/pixart/messenger/entities/ServiceDiscoveryResult.java b/src/main/java/de/pixart/messenger/entities/ServiceDiscoveryResult.java index cac20ba90..b0c3ebb77 100644 --- a/src/main/java/de/pixart/messenger/entities/ServiceDiscoveryResult.java +++ b/src/main/java/de/pixart/messenger/entities/ServiceDiscoveryResult.java @@ -22,328 +22,328 @@ import de.pixart.messenger.xmpp.forms.Field; import de.pixart.messenger.xmpp.stanzas.IqPacket; public class ServiceDiscoveryResult { - public static final String TABLENAME = "discovery_results"; - public static final String HASH = "hash"; - public static final String VER = "ver"; - public static final String RESULT = "result"; - - protected static String blankNull(String s) { - return s == null ? "" : s; - } - - public static class Identity implements Comparable { - protected final String category; - protected final String type; - protected final String lang; - protected final String name; - - public Identity(final String category, final String type, final String lang, final String name) { - this.category = category; - this.type = type; - this.lang = lang; - this.name = name; - } - - public Identity(final Element el) { - this( - el.getAttribute("category"), - el.getAttribute("type"), - el.getAttribute("xml:lang"), - el.getAttribute("name") - ); - } - - public Identity(final JSONObject o) { - - this( - o.optString("category", null), - o.optString("type", null), - o.optString("lang", null), - o.optString("name", null) - ); - } - - public String getCategory() { - return this.category; - } - - public String getType() { - return this.type; - } - - public String getLang() { - return this.lang; - } - - public String getName() { - return this.name; - } - - public int compareTo(Object other) { - Identity o = (Identity)other; - int r = blankNull(this.getCategory()).compareTo(blankNull(o.getCategory())); - if(r == 0) { - r = blankNull(this.getType()).compareTo(blankNull(o.getType())); - } - if(r == 0) { - r = blankNull(this.getLang()).compareTo(blankNull(o.getLang())); - } - if(r == 0) { - r = blankNull(this.getName()).compareTo(blankNull(o.getName())); - } - - return r; - } - - public JSONObject toJSON() { - try { - JSONObject o = new JSONObject(); - o.put("category", this.getCategory()); - o.put("type", this.getType()); - o.put("lang", this.getLang()); - o.put("name", this.getName()); - return o; - } catch(JSONException e) { - return null; - } - } - } - - protected final String hash; - protected final byte[] ver; - protected final List identities; - protected final List features; - protected final List forms; - - public ServiceDiscoveryResult(final IqPacket packet) { - this.identities = new ArrayList<>(); - this.features = new ArrayList<>(); - this.forms = new ArrayList<>(); - this.hash = "sha-1"; // We only support sha-1 for now - - final List elements = packet.query().getChildren(); - - for (final Element element : elements) { - if (element.getName().equals("identity")) { - Identity id = new Identity(element); - if (id.getType() != null && id.getCategory() != null) { - identities.add(id); - } - } else if (element.getName().equals("feature")) { - if (element.getAttribute("var") != null) { - features.add(element.getAttribute("var")); - } - } else if (element.getName().equals("x") && "jabber:x:data".equals(element.getAttribute("xmlns"))) { - forms.add(Data.parse(element)); - } - } - this.ver = this.mkCapHash(); - } - - public ServiceDiscoveryResult(String hash, byte[] ver, JSONObject o) throws JSONException { - this.identities = new ArrayList<>(); - this.features = new ArrayList<>(); - this.forms = new ArrayList<>(); - this.hash = hash; - this.ver = ver; - - JSONArray identities = o.optJSONArray("identities"); - if (identities != null) { - for (int i = 0; i < identities.length(); i++) { - this.identities.add(new Identity(identities.getJSONObject(i))); - } - } - JSONArray features = o.optJSONArray("features"); - if (features != null) { - for (int i = 0; i < features.length(); i++) { - this.features.add(features.getString(i)); - } - } - JSONArray forms = o.optJSONArray("forms"); - if (forms != null) { - for(int i = 0; i < forms.length(); i++) { - this.forms.add(createFormFromJSONObject(forms.getJSONObject(i))); - } - } - } - - private static Data createFormFromJSONObject(JSONObject o) { - Data data = new Data(); - JSONArray names = o.names(); - for(int i = 0; i < names.length(); ++i) { - try { - String name = names.getString(i); - JSONArray jsonValues = o.getJSONArray(name); - ArrayList values = new ArrayList<>(jsonValues.length()); - for(int j = 0; j < jsonValues.length(); ++j) { - values.add(jsonValues.getString(j)); - } - data.put(name, values); - } catch (Exception e) { - e.printStackTrace(); - } - } - return data; - } - - private static JSONObject createJSONFromForm(Data data) { - JSONObject object = new JSONObject(); - for(Field field : data.getFields()) { - try { - JSONArray jsonValues = new JSONArray(); - for(String value : field.getValues()) { - jsonValues.put(value); - } - object.put(field.getFieldName(), jsonValues); - } catch(Exception e) { - e.printStackTrace(); - } - } - try { - JSONArray jsonValues = new JSONArray(); - jsonValues.put(data.getFormType()); - object.put(Data.FORM_TYPE, jsonValues); - } catch(Exception e) { - e.printStackTrace(); - } - return object; - } - - public String getVer() { - return new String(Base64.encode(this.ver, Base64.DEFAULT)).trim(); - } - - public ServiceDiscoveryResult(Cursor cursor) throws JSONException { - this( - cursor.getString(cursor.getColumnIndex(HASH)), - Base64.decode(cursor.getString(cursor.getColumnIndex(VER)), Base64.DEFAULT), - new JSONObject(cursor.getString(cursor.getColumnIndex(RESULT))) - ); - } - - public List getIdentities() { - return this.identities; - } - - public List getFeatures() { - return this.features; - } - - public boolean hasIdentity(String category, String type) { - for(Identity id : this.getIdentities()) { - if((category == null || id.getCategory().equals(category)) && - (type == null || id.getType().equals(type))) { - return true; - } - } - - return false; - } - - public String getExtendedDiscoInformation(String formType, String name) { - for(Data form : this.forms) { - if (formType.equals(form.getFormType())) { - for(Field field: form.getFields()) { - if (name.equals(field.getFieldName())) { - return field.getValue(); - } - } - } - } - return null; - } - - protected byte[] mkCapHash() { - StringBuilder s = new StringBuilder(); - - List identities = this.getIdentities(); - Collections.sort(identities); - - for(Identity id : identities) { - s.append( - blankNull(id.getCategory()) + "/" + - blankNull(id.getType()) + "/" + - blankNull(id.getLang()) + "/" + - blankNull(id.getName()) + "<" - ); - } - - List features = this.getFeatures(); - Collections.sort(features); - - for (String feature : features) { - s.append(feature + "<"); - } - - Collections.sort(forms, new Comparator() { - @Override - public int compare(Data lhs, Data rhs) { - return lhs.getFormType().compareTo(rhs.getFormType()); - } - }); - - for(Data form : forms) { - s.append(form.getFormType() + "<"); - List fields = form.getFields(); - Collections.sort(fields, new Comparator() { - @Override - public int compare(Field lhs, Field rhs) { - return lhs.getFieldName().compareTo(rhs.getFieldName()); - } - }); - for(Field field : fields) { - s.append(field.getFieldName()+"<"); - List values = field.getValues(); - Collections.sort(values); - for(String value : values) { - s.append(value+"<"); - } - } - } - - MessageDigest md; - try { - md = MessageDigest.getInstance("SHA-1"); - } catch (NoSuchAlgorithmException e) { - return null; - } - - try { - return md.digest(s.toString().getBytes("UTF-8")); - } catch(UnsupportedEncodingException e) { - return null; - } - } - - public JSONObject toJSON() { - try { - JSONObject o = new JSONObject(); - - JSONArray ids = new JSONArray(); - for(Identity id : this.getIdentities()) { - ids.put(id.toJSON()); - } - o.put("identities", ids); - - o.put("features", new JSONArray(this.getFeatures())); - - JSONArray forms = new JSONArray(); - for(Data data : this.forms) { - forms.put(createJSONFromForm(data)); - } - o.put("forms", forms); - - return o; - } catch(JSONException e) { - return null; - } - } - - public ContentValues getContentValues() { - final ContentValues values = new ContentValues(); - values.put(HASH, this.hash); - values.put(VER, getVer()); - values.put(RESULT, this.toJSON().toString()); - return values; - } + public static final String TABLENAME = "discovery_results"; + public static final String HASH = "hash"; + public static final String VER = "ver"; + public static final String RESULT = "result"; + + protected static String blankNull(String s) { + return s == null ? "" : s; + } + + public static class Identity implements Comparable { + protected final String category; + protected final String type; + protected final String lang; + protected final String name; + + public Identity(final String category, final String type, final String lang, final String name) { + this.category = category; + this.type = type; + this.lang = lang; + this.name = name; + } + + public Identity(final Element el) { + this( + el.getAttribute("category"), + el.getAttribute("type"), + el.getAttribute("xml:lang"), + el.getAttribute("name") + ); + } + + public Identity(final JSONObject o) { + + this( + o.optString("category", null), + o.optString("type", null), + o.optString("lang", null), + o.optString("name", null) + ); + } + + public String getCategory() { + return this.category; + } + + public String getType() { + return this.type; + } + + public String getLang() { + return this.lang; + } + + public String getName() { + return this.name; + } + + public int compareTo(Object other) { + Identity o = (Identity) other; + int r = blankNull(this.getCategory()).compareTo(blankNull(o.getCategory())); + if (r == 0) { + r = blankNull(this.getType()).compareTo(blankNull(o.getType())); + } + if (r == 0) { + r = blankNull(this.getLang()).compareTo(blankNull(o.getLang())); + } + if (r == 0) { + r = blankNull(this.getName()).compareTo(blankNull(o.getName())); + } + + return r; + } + + public JSONObject toJSON() { + try { + JSONObject o = new JSONObject(); + o.put("category", this.getCategory()); + o.put("type", this.getType()); + o.put("lang", this.getLang()); + o.put("name", this.getName()); + return o; + } catch (JSONException e) { + return null; + } + } + } + + protected final String hash; + protected final byte[] ver; + protected final List identities; + protected final List features; + protected final List forms; + + public ServiceDiscoveryResult(final IqPacket packet) { + this.identities = new ArrayList<>(); + this.features = new ArrayList<>(); + this.forms = new ArrayList<>(); + this.hash = "sha-1"; // We only support sha-1 for now + + final List elements = packet.query().getChildren(); + + for (final Element element : elements) { + if (element.getName().equals("identity")) { + Identity id = new Identity(element); + if (id.getType() != null && id.getCategory() != null) { + identities.add(id); + } + } else if (element.getName().equals("feature")) { + if (element.getAttribute("var") != null) { + features.add(element.getAttribute("var")); + } + } else if (element.getName().equals("x") && "jabber:x:data".equals(element.getAttribute("xmlns"))) { + forms.add(Data.parse(element)); + } + } + this.ver = this.mkCapHash(); + } + + public ServiceDiscoveryResult(String hash, byte[] ver, JSONObject o) throws JSONException { + this.identities = new ArrayList<>(); + this.features = new ArrayList<>(); + this.forms = new ArrayList<>(); + this.hash = hash; + this.ver = ver; + + JSONArray identities = o.optJSONArray("identities"); + if (identities != null) { + for (int i = 0; i < identities.length(); i++) { + this.identities.add(new Identity(identities.getJSONObject(i))); + } + } + JSONArray features = o.optJSONArray("features"); + if (features != null) { + for (int i = 0; i < features.length(); i++) { + this.features.add(features.getString(i)); + } + } + JSONArray forms = o.optJSONArray("forms"); + if (forms != null) { + for (int i = 0; i < forms.length(); i++) { + this.forms.add(createFormFromJSONObject(forms.getJSONObject(i))); + } + } + } + + private static Data createFormFromJSONObject(JSONObject o) { + Data data = new Data(); + JSONArray names = o.names(); + for (int i = 0; i < names.length(); ++i) { + try { + String name = names.getString(i); + JSONArray jsonValues = o.getJSONArray(name); + ArrayList values = new ArrayList<>(jsonValues.length()); + for (int j = 0; j < jsonValues.length(); ++j) { + values.add(jsonValues.getString(j)); + } + data.put(name, values); + } catch (Exception e) { + e.printStackTrace(); + } + } + return data; + } + + private static JSONObject createJSONFromForm(Data data) { + JSONObject object = new JSONObject(); + for (Field field : data.getFields()) { + try { + JSONArray jsonValues = new JSONArray(); + for (String value : field.getValues()) { + jsonValues.put(value); + } + object.put(field.getFieldName(), jsonValues); + } catch (Exception e) { + e.printStackTrace(); + } + } + try { + JSONArray jsonValues = new JSONArray(); + jsonValues.put(data.getFormType()); + object.put(Data.FORM_TYPE, jsonValues); + } catch (Exception e) { + e.printStackTrace(); + } + return object; + } + + public String getVer() { + return new String(Base64.encode(this.ver, Base64.DEFAULT)).trim(); + } + + public ServiceDiscoveryResult(Cursor cursor) throws JSONException { + this( + cursor.getString(cursor.getColumnIndex(HASH)), + Base64.decode(cursor.getString(cursor.getColumnIndex(VER)), Base64.DEFAULT), + new JSONObject(cursor.getString(cursor.getColumnIndex(RESULT))) + ); + } + + public List getIdentities() { + return this.identities; + } + + public List getFeatures() { + return this.features; + } + + public boolean hasIdentity(String category, String type) { + for (Identity id : this.getIdentities()) { + if ((category == null || id.getCategory().equals(category)) && + (type == null || id.getType().equals(type))) { + return true; + } + } + + return false; + } + + public String getExtendedDiscoInformation(String formType, String name) { + for (Data form : this.forms) { + if (formType.equals(form.getFormType())) { + for (Field field : form.getFields()) { + if (name.equals(field.getFieldName())) { + return field.getValue(); + } + } + } + } + return null; + } + + protected byte[] mkCapHash() { + StringBuilder s = new StringBuilder(); + + List identities = this.getIdentities(); + Collections.sort(identities); + + for (Identity id : identities) { + s.append( + blankNull(id.getCategory()) + "/" + + blankNull(id.getType()) + "/" + + blankNull(id.getLang()) + "/" + + blankNull(id.getName()) + "<" + ); + } + + List features = this.getFeatures(); + Collections.sort(features); + + for (String feature : features) { + s.append(feature + "<"); + } + + Collections.sort(forms, new Comparator() { + @Override + public int compare(Data lhs, Data rhs) { + return lhs.getFormType().compareTo(rhs.getFormType()); + } + }); + + for (Data form : forms) { + s.append(form.getFormType() + "<"); + List fields = form.getFields(); + Collections.sort(fields, new Comparator() { + @Override + public int compare(Field lhs, Field rhs) { + return lhs.getFieldName().compareTo(rhs.getFieldName()); + } + }); + for (Field field : fields) { + s.append(field.getFieldName() + "<"); + List values = field.getValues(); + Collections.sort(values); + for (String value : values) { + s.append(value + "<"); + } + } + } + + MessageDigest md; + try { + md = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException e) { + return null; + } + + try { + return md.digest(s.toString().getBytes("UTF-8")); + } catch (UnsupportedEncodingException e) { + return null; + } + } + + public JSONObject toJSON() { + try { + JSONObject o = new JSONObject(); + + JSONArray ids = new JSONArray(); + for (Identity id : this.getIdentities()) { + ids.put(id.toJSON()); + } + o.put("identities", ids); + + o.put("features", new JSONArray(this.getFeatures())); + + JSONArray forms = new JSONArray(); + for (Data data : this.forms) { + forms.put(createJSONFromForm(data)); + } + o.put("forms", forms); + + return o; + } catch (JSONException e) { + return null; + } + } + + public ContentValues getContentValues() { + final ContentValues values = new ContentValues(); + values.put(HASH, this.hash); + values.put(VER, getVer()); + values.put(RESULT, this.toJSON().toString()); + return values; + } } diff --git a/src/main/java/de/pixart/messenger/entities/Transferable.java b/src/main/java/de/pixart/messenger/entities/Transferable.java index 14132ac38..c07ec750d 100644 --- a/src/main/java/de/pixart/messenger/entities/Transferable.java +++ b/src/main/java/de/pixart/messenger/entities/Transferable.java @@ -54,7 +54,7 @@ public interface Transferable { //compressed "zip", "rar" - ); + ); int STATUS_UNKNOWN = 0x200; int STATUS_CHECKING = 0x201; diff --git a/src/main/java/de/pixart/messenger/entities/TransferablePlaceholder.java b/src/main/java/de/pixart/messenger/entities/TransferablePlaceholder.java index 59f1dc846..e22984f5a 100644 --- a/src/main/java/de/pixart/messenger/entities/TransferablePlaceholder.java +++ b/src/main/java/de/pixart/messenger/entities/TransferablePlaceholder.java @@ -2,33 +2,34 @@ package de.pixart.messenger.entities; public class TransferablePlaceholder implements Transferable { - private int status; - - public TransferablePlaceholder(int status) { - this.status = status; - } - @Override - public boolean start() { - return false; - } - - @Override - public int getStatus() { - return status; - } - - @Override - public long getFileSize() { - return 0; - } - - @Override - public int getProgress() { - return 0; - } - - @Override - public void cancel() { - - } + private int status; + + public TransferablePlaceholder(int status) { + this.status = status; + } + + @Override + public boolean start() { + return false; + } + + @Override + public int getStatus() { + return status; + } + + @Override + public long getFileSize() { + return 0; + } + + @Override + public int getProgress() { + return 0; + } + + @Override + public void cancel() { + + } } diff --git a/src/main/java/de/pixart/messenger/generator/AbstractGenerator.java b/src/main/java/de/pixart/messenger/generator/AbstractGenerator.java index 829b033fe..33ce81859 100644 --- a/src/main/java/de/pixart/messenger/generator/AbstractGenerator.java +++ b/src/main/java/de/pixart/messenger/generator/AbstractGenerator.java @@ -20,105 +20,105 @@ import de.pixart.messenger.utils.PhoneHelper; import de.pixart.messenger.xmpp.jingle.stanzas.Content; public abstract class AbstractGenerator { - private final String[] FEATURES = { - "urn:xmpp:jingle:1", - Content.Version.FT_3.getNamespace(), - Content.Version.FT_4.getNamespace(), - "urn:xmpp:jingle:transports:s5b:1", - "urn:xmpp:jingle:transports:ibb:1", - "http://jabber.org/protocol/muc", - "jabber:x:conference", - "http://jabber.org/protocol/caps", - "http://jabber.org/protocol/disco#info", - "urn:xmpp:avatar:metadata+notify", - "http://jabber.org/protocol/nick+notify", - "urn:xmpp:ping", - "jabber:iq:version", - "http://jabber.org/protocol/chatstates" - }; - private final String[] MESSAGE_CONFIRMATION_FEATURES = { - "urn:xmpp:chat-markers:0", - "urn:xmpp:receipts" - }; - private final String[] MESSAGE_CORRECTION_FEATURES = { - "urn:xmpp:message-correct:0" - }; - private final String[] PRIVACY_SENSITIVE = { - "urn:xmpp:time" //XEP-0202: Entity Time leaks time zone - }; - private final String[] OTR = { - "urn:xmpp:otr:0" - }; - private String mVersion = null; + private final String[] FEATURES = { + "urn:xmpp:jingle:1", + Content.Version.FT_3.getNamespace(), + Content.Version.FT_4.getNamespace(), + "urn:xmpp:jingle:transports:s5b:1", + "urn:xmpp:jingle:transports:ibb:1", + "http://jabber.org/protocol/muc", + "jabber:x:conference", + "http://jabber.org/protocol/caps", + "http://jabber.org/protocol/disco#info", + "urn:xmpp:avatar:metadata+notify", + "http://jabber.org/protocol/nick+notify", + "urn:xmpp:ping", + "jabber:iq:version", + "http://jabber.org/protocol/chatstates" + }; + private final String[] MESSAGE_CONFIRMATION_FEATURES = { + "urn:xmpp:chat-markers:0", + "urn:xmpp:receipts" + }; + private final String[] MESSAGE_CORRECTION_FEATURES = { + "urn:xmpp:message-correct:0" + }; + private final String[] PRIVACY_SENSITIVE = { + "urn:xmpp:time" //XEP-0202: Entity Time leaks time zone + }; + private final String[] OTR = { + "urn:xmpp:otr:0" + }; + private String mVersion = null; - private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US); + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US); - protected XmppConnectionService mXmppConnectionService; + protected XmppConnectionService mXmppConnectionService; - protected AbstractGenerator(XmppConnectionService service) { - this.mXmppConnectionService = service; - } + protected AbstractGenerator(XmppConnectionService service) { + this.mXmppConnectionService = service; + } - protected String getIdentityVersion() { - if (mVersion == null) { - this.mVersion = PhoneHelper.getVersionName(mXmppConnectionService); - } - return this.mVersion; - } + protected String getIdentityVersion() { + if (mVersion == null) { + this.mVersion = PhoneHelper.getVersionName(mXmppConnectionService); + } + return this.mVersion; + } - public String getIdentityName() { - return mXmppConnectionService.getString(R.string.app_name) + " " + getIdentityVersion(); - } + public String getIdentityName() { + return mXmppConnectionService.getString(R.string.app_name) + " " + getIdentityVersion(); + } - public String getIdentityType() { - if ("chromium".equals(android.os.Build.BRAND)) { - return "pc"; - } else { - return mXmppConnectionService.getString(R.string.default_resource).toLowerCase(); - } - } + public String getIdentityType() { + if ("chromium".equals(android.os.Build.BRAND)) { + return "pc"; + } else { + return mXmppConnectionService.getString(R.string.default_resource).toLowerCase(); + } + } - public String getCapHash() { - StringBuilder s = new StringBuilder(); - s.append("client/" + getIdentityType() + "//" + getIdentityName() + "<"); - MessageDigest md; - try { - md = MessageDigest.getInstance("SHA-1"); - } catch (NoSuchAlgorithmException e) { - return null; - } + public String getCapHash() { + StringBuilder s = new StringBuilder(); + s.append("client/" + getIdentityType() + "//" + getIdentityName() + "<"); + MessageDigest md; + try { + md = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException e) { + return null; + } - for (String feature : getFeatures()) { - s.append(feature + "<"); - } - byte[] sha1 = md.digest(s.toString().getBytes()); - return new String(Base64.encode(sha1, Base64.DEFAULT)).trim(); - } + for (String feature : getFeatures()) { + s.append(feature + "<"); + } + byte[] sha1 = md.digest(s.toString().getBytes()); + return new String(Base64.encode(sha1, Base64.DEFAULT)).trim(); + } - public static String getTimestamp(long time) { - DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); - return DATE_FORMAT.format(time); - } + public static String getTimestamp(long time) { + DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); + return DATE_FORMAT.format(time); + } - public List getFeatures() { - ArrayList features = new ArrayList<>(); - features.addAll(Arrays.asList(FEATURES)); - if (mXmppConnectionService.confirmMessages()) { - features.addAll(Arrays.asList(MESSAGE_CONFIRMATION_FEATURES)); - } - if (mXmppConnectionService.allowMessageCorrection()) { - features.addAll(Arrays.asList(MESSAGE_CORRECTION_FEATURES)); - } - if (Config.supportOmemo()) { - features.add(AxolotlService.PEP_DEVICE_LIST_NOTIFY); - } - if (!mXmppConnectionService.useTorToConnect()) { - features.addAll(Arrays.asList(PRIVACY_SENSITIVE)); - } - if (Config.supportOtr()) { - features.addAll(Arrays.asList(OTR)); - } - Collections.sort(features); - return features; - } + public List getFeatures() { + ArrayList features = new ArrayList<>(); + features.addAll(Arrays.asList(FEATURES)); + if (mXmppConnectionService.confirmMessages()) { + features.addAll(Arrays.asList(MESSAGE_CONFIRMATION_FEATURES)); + } + if (mXmppConnectionService.allowMessageCorrection()) { + features.addAll(Arrays.asList(MESSAGE_CORRECTION_FEATURES)); + } + if (Config.supportOmemo()) { + features.add(AxolotlService.PEP_DEVICE_LIST_NOTIFY); + } + if (!mXmppConnectionService.useTorToConnect()) { + features.addAll(Arrays.asList(PRIVACY_SENSITIVE)); + } + if (Config.supportOtr()) { + features.addAll(Arrays.asList(OTR)); + } + Collections.sort(features); + return features; + } } diff --git a/src/main/java/de/pixart/messenger/generator/IqGenerator.java b/src/main/java/de/pixart/messenger/generator/IqGenerator.java index fd7177a8c..e2b462f76 100644 --- a/src/main/java/de/pixart/messenger/generator/IqGenerator.java +++ b/src/main/java/de/pixart/messenger/generator/IqGenerator.java @@ -34,347 +34,348 @@ import de.pixart.messenger.xmpp.stanzas.IqPacket; public class IqGenerator extends AbstractGenerator { - public IqGenerator(final XmppConnectionService service) { - super(service); - } - - public IqPacket discoResponse(final IqPacket request) { - final IqPacket packet = new IqPacket(IqPacket.TYPE.RESULT); - packet.setId(request.getId()); - packet.setTo(request.getFrom()); - final Element query = packet.addChild("query", - "http://jabber.org/protocol/disco#info"); - query.setAttribute("node", request.query().getAttribute("node")); - final Element identity = query.addChild("identity"); - identity.setAttribute("category", "client"); - identity.setAttribute("type", getIdentityType()); - identity.setAttribute("name", getIdentityName()); - for (final String feature : getFeatures()) { - query.addChild("feature").setAttribute("var", feature); - } - return packet; - } - - public IqPacket versionResponse(final IqPacket request) { - final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT); - Element query = packet.query("jabber:iq:version"); - query.addChild("name").setContent(mXmppConnectionService.getString(R.string.app_name)); - query.addChild("version").setContent(getIdentityVersion()); - if ("chromium".equals(android.os.Build.BRAND)) { - query.addChild("os").setContent("Chrome OS"); - } else{ - query.addChild("os").setContent("Android"); - } - return packet; - } - - public IqPacket entityTimeResponse(IqPacket request) { - final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT); - Element time = packet.addChild("time","urn:xmpp:time"); - final long now = System.currentTimeMillis(); - time.addChild("utc").setContent(getTimestamp(now)); - TimeZone ourTimezone = TimeZone.getDefault(); - long offsetSeconds = ourTimezone.getOffset(now) / 1000; - long offsetMinutes = offsetSeconds % (60 * 60); - long offsetHours = offsetSeconds / (60 * 60); - time.addChild("tzo").setContent(String.format("%02d",offsetHours)+":"+String.format("%02d",offsetMinutes)); - return packet; - } - - protected IqPacket publish(final String node, final Element item) { - final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); - final Element pubsub = packet.addChild("pubsub", - "http://jabber.org/protocol/pubsub"); - final Element publish = pubsub.addChild("publish"); - publish.setAttribute("node", node); - publish.addChild(item); - return packet; - } - - protected IqPacket retrieve(String node, Element item) { - final IqPacket packet = new IqPacket(IqPacket.TYPE.GET); - final Element pubsub = packet.addChild("pubsub", - "http://jabber.org/protocol/pubsub"); - final Element items = pubsub.addChild("items"); - items.setAttribute("node", node); - if (item != null) { - items.addChild(item); - } - return packet; - } - - public IqPacket publishNick(String nick) { - final Element item = new Element("item"); - item.addChild("nick","http://jabber.org/protocol/nick").setContent(nick); - return publish("http://jabber.org/protocol/nick", item); - } - - public IqPacket publishAvatar(Avatar avatar) { - final Element item = new Element("item"); - item.setAttribute("id", avatar.sha1sum); - final Element data = item.addChild("data", "urn:xmpp:avatar:data"); - data.setContent(avatar.image); - return publish("urn:xmpp:avatar:data", item); - } - - public IqPacket publishAvatarMetadata(final Avatar avatar) { - final Element item = new Element("item"); - item.setAttribute("id", avatar.sha1sum); - final Element metadata = item - .addChild("metadata", "urn:xmpp:avatar:metadata"); - final Element info = metadata.addChild("info"); - info.setAttribute("bytes", avatar.size); - info.setAttribute("id", avatar.sha1sum); - info.setAttribute("height", avatar.height); - info.setAttribute("width", avatar.height); - info.setAttribute("type", avatar.type); - return publish("urn:xmpp:avatar:metadata", item); - } - - public IqPacket retrievePepAvatar(final Avatar avatar) { - final Element item = new Element("item"); - item.setAttribute("id", avatar.sha1sum); - final IqPacket packet = retrieve("urn:xmpp:avatar:data", item); - packet.setTo(avatar.owner); - return packet; - } - - public IqPacket retrieveVcardAvatar(final Avatar avatar) { - final IqPacket packet = new IqPacket(IqPacket.TYPE.GET); - packet.setTo(avatar.owner); - packet.addChild("vCard", "vcard-temp"); - return packet; - } - - public IqPacket retrieveAvatarMetaData(final Jid to) { - final IqPacket packet = retrieve("urn:xmpp:avatar:metadata", null); - if (to != null) { - packet.setTo(to); - } - return packet; - } - - public IqPacket retrieveDeviceIds(final Jid to) { - final IqPacket packet = retrieve(AxolotlService.PEP_DEVICE_LIST, null); - if(to != null) { - packet.setTo(to); - } - return packet; - } - - public IqPacket retrieveBundlesForDevice(final Jid to, final int deviceid) { - final IqPacket packet = retrieve(AxolotlService.PEP_BUNDLES+":"+deviceid, null); - packet.setTo(to); - return packet; - } - - public IqPacket retrieveVerificationForDevice(final Jid to, final int deviceid) { - final IqPacket packet = retrieve(AxolotlService.PEP_VERIFICATION+":"+deviceid, null); - packet.setTo(to); - return packet; - } - - public IqPacket publishDeviceIds(final Set ids) { - final Element item = new Element("item"); - final Element list = item.addChild("list", AxolotlService.PEP_PREFIX); - for(Integer id:ids) { - final Element device = new Element("device"); - device.setAttribute("id", id); - list.addChild(device); - } - return publish(AxolotlService.PEP_DEVICE_LIST, item); - } - - public IqPacket publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey, - final Set preKeyRecords, final int deviceId) { - final Element item = new Element("item"); - final Element bundle = item.addChild("bundle", AxolotlService.PEP_PREFIX); - final Element signedPreKeyPublic = bundle.addChild("signedPreKeyPublic"); - signedPreKeyPublic.setAttribute("signedPreKeyId", signedPreKeyRecord.getId()); - ECPublicKey publicKey = signedPreKeyRecord.getKeyPair().getPublicKey(); - signedPreKeyPublic.setContent(Base64.encodeToString(publicKey.serialize(),Base64.DEFAULT)); - final Element signedPreKeySignature = bundle.addChild("signedPreKeySignature"); - signedPreKeySignature.setContent(Base64.encodeToString(signedPreKeyRecord.getSignature(),Base64.DEFAULT)); - final Element identityKeyElement = bundle.addChild("identityKey"); - identityKeyElement.setContent(Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT)); - - final Element prekeys = bundle.addChild("prekeys", AxolotlService.PEP_PREFIX); - for(PreKeyRecord preKeyRecord:preKeyRecords) { - final Element prekey = prekeys.addChild("preKeyPublic"); - prekey.setAttribute("preKeyId", preKeyRecord.getId()); - prekey.setContent(Base64.encodeToString(preKeyRecord.getKeyPair().getPublicKey().serialize(), Base64.DEFAULT)); - } - - return publish(AxolotlService.PEP_BUNDLES+":"+deviceId, item); - } - - public IqPacket publishVerification(byte[] signature, X509Certificate[] certificates, final int deviceId) { - final Element item = new Element("item"); - final Element verification = item.addChild("verification", AxolotlService.PEP_PREFIX); - final Element chain = verification.addChild("chain"); - for(int i = 0; i < certificates.length; ++i) { - try { - Element certificate = chain.addChild("certificate"); - certificate.setContent(Base64.encodeToString(certificates[i].getEncoded(), Base64.DEFAULT)); - certificate.setAttribute("index",i); - } catch (CertificateEncodingException e) { - Log.d(Config.LOGTAG, "could not encode certificate"); - } - } - verification.addChild("signature").setContent(Base64.encodeToString(signature, Base64.DEFAULT)); - return publish(AxolotlService.PEP_VERIFICATION+":"+deviceId, item); - } - - public IqPacket queryMessageArchiveManagement(final MessageArchiveService.Query mam) { - final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); - final Element query = packet.query("urn:xmpp:mam:0"); - query.setAttribute("queryid", mam.getQueryId()); - final Data data = new Data(); - data.setFormType("urn:xmpp:mam:0"); - if (mam.muc()) { - packet.setTo(mam.getWith()); - } else if (mam.getWith()!=null) { - data.put("with", mam.getWith().toString()); - } - data.put("start", getTimestamp(mam.getStart())); - data.put("end", getTimestamp(mam.getEnd())); - data.submit(); - query.addChild(data); - if (mam.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) { - query.addChild("set", "http://jabber.org/protocol/rsm").addChild("before").setContent(mam.getReference()); - } else if (mam.getReference() != null) { - query.addChild("set", "http://jabber.org/protocol/rsm").addChild("after").setContent(mam.getReference()); - } - return packet; - } - public IqPacket generateGetBlockList() { - final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); - iq.addChild("blocklist", Xmlns.BLOCKING); - - return iq; - } - - public IqPacket generateSetBlockRequest(final Jid jid, boolean reportSpam) { - final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); - final Element block = iq.addChild("block", Xmlns.BLOCKING); - final Element item = block.addChild("item").setAttribute("jid", jid.toBareJid().toString()); - if (reportSpam) { - item.addChild("report", "urn:xmpp:reporting:0").addChild("spam"); - } - Log.d(Config.LOGTAG,iq.toString()); - return iq; - } - - public IqPacket generateSetUnblockRequest(final Jid jid) { - final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); - final Element block = iq.addChild("unblock", Xmlns.BLOCKING); - block.addChild("item").setAttribute("jid", jid.toBareJid().toString()); - return iq; - } - - public IqPacket generateSetPassword(final Account account, final String newPassword) { - final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); - packet.setTo(account.getServer()); - final Element query = packet.addChild("query", Xmlns.REGISTER); - final Jid jid = account.getJid(); - query.addChild("username").setContent(jid.getLocalpart()); - query.addChild("password").setContent(newPassword); - return packet; - } - - public IqPacket changeAffiliation(Conversation conference, Jid jid, String affiliation) { - List jids = new ArrayList<>(); - jids.add(jid); - return changeAffiliation(conference,jids,affiliation); - } - - public IqPacket changeAffiliation(Conversation conference, List jids, String affiliation) { - IqPacket packet = new IqPacket(IqPacket.TYPE.SET); - packet.setTo(conference.getJid().toBareJid()); - packet.setFrom(conference.getAccount().getJid()); - Element query = packet.query("http://jabber.org/protocol/muc#admin"); - for(Jid jid : jids) { - Element item = query.addChild("item"); - item.setAttribute("jid", jid.toString()); - item.setAttribute("affiliation", affiliation); - } - return packet; - } - - public IqPacket changeRole(Conversation conference, String nick, String role) { - IqPacket packet = new IqPacket(IqPacket.TYPE.SET); - packet.setTo(conference.getJid().toBareJid()); - packet.setFrom(conference.getAccount().getJid()); - Element item = packet.query("http://jabber.org/protocol/muc#admin").addChild("item"); - item.setAttribute("nick", nick); - item.setAttribute("role", role); - return packet; - } - - public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file, String mime) { - IqPacket packet = new IqPacket(IqPacket.TYPE.GET); - packet.setTo(host); - Element request = packet.addChild("request", Xmlns.HTTP_UPLOAD); - request.addChild("filename").setContent(file.getName()); - request.addChild("size").setContent(String.valueOf(file.getExpectedSize())); - if (mime != null) { - request.addChild("content-type").setContent(mime); - } - return packet; - } - - public IqPacket generateCreateAccountWithCaptcha(Account account, String id, Data data) { - final IqPacket register = new IqPacket(IqPacket.TYPE.SET); - register.setFrom(account.getJid().toBareJid()); - register.setTo(account.getServer()); - register.setId(id); - Element query = register.query("jabber:iq:register"); - if (data != null) { - query.addChild(data); - } - return register; - } - - public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId) { - IqPacket packet = new IqPacket(IqPacket.TYPE.SET); - packet.setTo(appServer); - Element command = packet.addChild("command", "http://jabber.org/protocol/commands"); - command.setAttribute("node","register-push-gcm"); - command.setAttribute("action","execute"); - Data data = new Data(); - data.put("token", token); - data.put("device-id", deviceId); - data.submit(); - command.addChild(data); - return packet; - } - - public IqPacket enablePush(Jid jid, String node, String secret) { - IqPacket packet = new IqPacket(IqPacket.TYPE.SET); - Element enable = packet.addChild("enable","urn:xmpp:push:0"); - enable.setAttribute("jid",jid.toString()); - enable.setAttribute("node", node); - Data data = new Data(); - data.setFormType("http://jabber.org/protocol/pubsub#publish-options"); - data.put("secret",secret); - data.submit(); - enable.addChild(data); - return packet; - } - - public IqPacket queryAffiliation(Conversation conversation, String affiliation) { - IqPacket packet = new IqPacket(IqPacket.TYPE.GET); - packet.setTo(conversation.getJid().toBareJid()); - packet.query("http://jabber.org/protocol/muc#admin").addChild("item").setAttribute("affiliation",affiliation); - return packet; - } - - public static Bundle defaultRoomConfiguration() { - Bundle options = new Bundle(); - options.putString("muc#roomconfig_persistentroom", "1"); - options.putString("muc#roomconfig_membersonly", "1"); - options.putString("muc#roomconfig_publicroom", "0"); - options.putString("muc#roomconfig_whois", "anyone"); - return options; - } + public IqGenerator(final XmppConnectionService service) { + super(service); + } + + public IqPacket discoResponse(final IqPacket request) { + final IqPacket packet = new IqPacket(IqPacket.TYPE.RESULT); + packet.setId(request.getId()); + packet.setTo(request.getFrom()); + final Element query = packet.addChild("query", + "http://jabber.org/protocol/disco#info"); + query.setAttribute("node", request.query().getAttribute("node")); + final Element identity = query.addChild("identity"); + identity.setAttribute("category", "client"); + identity.setAttribute("type", getIdentityType()); + identity.setAttribute("name", getIdentityName()); + for (final String feature : getFeatures()) { + query.addChild("feature").setAttribute("var", feature); + } + return packet; + } + + public IqPacket versionResponse(final IqPacket request) { + final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT); + Element query = packet.query("jabber:iq:version"); + query.addChild("name").setContent(mXmppConnectionService.getString(R.string.app_name)); + query.addChild("version").setContent(getIdentityVersion()); + if ("chromium".equals(android.os.Build.BRAND)) { + query.addChild("os").setContent("Chrome OS"); + } else { + query.addChild("os").setContent("Android"); + } + return packet; + } + + public IqPacket entityTimeResponse(IqPacket request) { + final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT); + Element time = packet.addChild("time", "urn:xmpp:time"); + final long now = System.currentTimeMillis(); + time.addChild("utc").setContent(getTimestamp(now)); + TimeZone ourTimezone = TimeZone.getDefault(); + long offsetSeconds = ourTimezone.getOffset(now) / 1000; + long offsetMinutes = offsetSeconds % (60 * 60); + long offsetHours = offsetSeconds / (60 * 60); + time.addChild("tzo").setContent(String.format("%02d", offsetHours) + ":" + String.format("%02d", offsetMinutes)); + return packet; + } + + protected IqPacket publish(final String node, final Element item) { + final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + final Element pubsub = packet.addChild("pubsub", + "http://jabber.org/protocol/pubsub"); + final Element publish = pubsub.addChild("publish"); + publish.setAttribute("node", node); + publish.addChild(item); + return packet; + } + + protected IqPacket retrieve(String node, Element item) { + final IqPacket packet = new IqPacket(IqPacket.TYPE.GET); + final Element pubsub = packet.addChild("pubsub", + "http://jabber.org/protocol/pubsub"); + final Element items = pubsub.addChild("items"); + items.setAttribute("node", node); + if (item != null) { + items.addChild(item); + } + return packet; + } + + public IqPacket publishNick(String nick) { + final Element item = new Element("item"); + item.addChild("nick", "http://jabber.org/protocol/nick").setContent(nick); + return publish("http://jabber.org/protocol/nick", item); + } + + public IqPacket publishAvatar(Avatar avatar) { + final Element item = new Element("item"); + item.setAttribute("id", avatar.sha1sum); + final Element data = item.addChild("data", "urn:xmpp:avatar:data"); + data.setContent(avatar.image); + return publish("urn:xmpp:avatar:data", item); + } + + public IqPacket publishAvatarMetadata(final Avatar avatar) { + final Element item = new Element("item"); + item.setAttribute("id", avatar.sha1sum); + final Element metadata = item + .addChild("metadata", "urn:xmpp:avatar:metadata"); + final Element info = metadata.addChild("info"); + info.setAttribute("bytes", avatar.size); + info.setAttribute("id", avatar.sha1sum); + info.setAttribute("height", avatar.height); + info.setAttribute("width", avatar.height); + info.setAttribute("type", avatar.type); + return publish("urn:xmpp:avatar:metadata", item); + } + + public IqPacket retrievePepAvatar(final Avatar avatar) { + final Element item = new Element("item"); + item.setAttribute("id", avatar.sha1sum); + final IqPacket packet = retrieve("urn:xmpp:avatar:data", item); + packet.setTo(avatar.owner); + return packet; + } + + public IqPacket retrieveVcardAvatar(final Avatar avatar) { + final IqPacket packet = new IqPacket(IqPacket.TYPE.GET); + packet.setTo(avatar.owner); + packet.addChild("vCard", "vcard-temp"); + return packet; + } + + public IqPacket retrieveAvatarMetaData(final Jid to) { + final IqPacket packet = retrieve("urn:xmpp:avatar:metadata", null); + if (to != null) { + packet.setTo(to); + } + return packet; + } + + public IqPacket retrieveDeviceIds(final Jid to) { + final IqPacket packet = retrieve(AxolotlService.PEP_DEVICE_LIST, null); + if (to != null) { + packet.setTo(to); + } + return packet; + } + + public IqPacket retrieveBundlesForDevice(final Jid to, final int deviceid) { + final IqPacket packet = retrieve(AxolotlService.PEP_BUNDLES + ":" + deviceid, null); + packet.setTo(to); + return packet; + } + + public IqPacket retrieveVerificationForDevice(final Jid to, final int deviceid) { + final IqPacket packet = retrieve(AxolotlService.PEP_VERIFICATION + ":" + deviceid, null); + packet.setTo(to); + return packet; + } + + public IqPacket publishDeviceIds(final Set ids) { + final Element item = new Element("item"); + final Element list = item.addChild("list", AxolotlService.PEP_PREFIX); + for (Integer id : ids) { + final Element device = new Element("device"); + device.setAttribute("id", id); + list.addChild(device); + } + return publish(AxolotlService.PEP_DEVICE_LIST, item); + } + + public IqPacket publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey, + final Set preKeyRecords, final int deviceId) { + final Element item = new Element("item"); + final Element bundle = item.addChild("bundle", AxolotlService.PEP_PREFIX); + final Element signedPreKeyPublic = bundle.addChild("signedPreKeyPublic"); + signedPreKeyPublic.setAttribute("signedPreKeyId", signedPreKeyRecord.getId()); + ECPublicKey publicKey = signedPreKeyRecord.getKeyPair().getPublicKey(); + signedPreKeyPublic.setContent(Base64.encodeToString(publicKey.serialize(), Base64.DEFAULT)); + final Element signedPreKeySignature = bundle.addChild("signedPreKeySignature"); + signedPreKeySignature.setContent(Base64.encodeToString(signedPreKeyRecord.getSignature(), Base64.DEFAULT)); + final Element identityKeyElement = bundle.addChild("identityKey"); + identityKeyElement.setContent(Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT)); + + final Element prekeys = bundle.addChild("prekeys", AxolotlService.PEP_PREFIX); + for (PreKeyRecord preKeyRecord : preKeyRecords) { + final Element prekey = prekeys.addChild("preKeyPublic"); + prekey.setAttribute("preKeyId", preKeyRecord.getId()); + prekey.setContent(Base64.encodeToString(preKeyRecord.getKeyPair().getPublicKey().serialize(), Base64.DEFAULT)); + } + + return publish(AxolotlService.PEP_BUNDLES + ":" + deviceId, item); + } + + public IqPacket publishVerification(byte[] signature, X509Certificate[] certificates, final int deviceId) { + final Element item = new Element("item"); + final Element verification = item.addChild("verification", AxolotlService.PEP_PREFIX); + final Element chain = verification.addChild("chain"); + for (int i = 0; i < certificates.length; ++i) { + try { + Element certificate = chain.addChild("certificate"); + certificate.setContent(Base64.encodeToString(certificates[i].getEncoded(), Base64.DEFAULT)); + certificate.setAttribute("index", i); + } catch (CertificateEncodingException e) { + Log.d(Config.LOGTAG, "could not encode certificate"); + } + } + verification.addChild("signature").setContent(Base64.encodeToString(signature, Base64.DEFAULT)); + return publish(AxolotlService.PEP_VERIFICATION + ":" + deviceId, item); + } + + public IqPacket queryMessageArchiveManagement(final MessageArchiveService.Query mam) { + final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + final Element query = packet.query("urn:xmpp:mam:0"); + query.setAttribute("queryid", mam.getQueryId()); + final Data data = new Data(); + data.setFormType("urn:xmpp:mam:0"); + if (mam.muc()) { + packet.setTo(mam.getWith()); + } else if (mam.getWith() != null) { + data.put("with", mam.getWith().toString()); + } + data.put("start", getTimestamp(mam.getStart())); + data.put("end", getTimestamp(mam.getEnd())); + data.submit(); + query.addChild(data); + if (mam.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) { + query.addChild("set", "http://jabber.org/protocol/rsm").addChild("before").setContent(mam.getReference()); + } else if (mam.getReference() != null) { + query.addChild("set", "http://jabber.org/protocol/rsm").addChild("after").setContent(mam.getReference()); + } + return packet; + } + + public IqPacket generateGetBlockList() { + final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); + iq.addChild("blocklist", Xmlns.BLOCKING); + + return iq; + } + + public IqPacket generateSetBlockRequest(final Jid jid, boolean reportSpam) { + final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); + final Element block = iq.addChild("block", Xmlns.BLOCKING); + final Element item = block.addChild("item").setAttribute("jid", jid.toBareJid().toString()); + if (reportSpam) { + item.addChild("report", "urn:xmpp:reporting:0").addChild("spam"); + } + Log.d(Config.LOGTAG, iq.toString()); + return iq; + } + + public IqPacket generateSetUnblockRequest(final Jid jid) { + final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); + final Element block = iq.addChild("unblock", Xmlns.BLOCKING); + block.addChild("item").setAttribute("jid", jid.toBareJid().toString()); + return iq; + } + + public IqPacket generateSetPassword(final Account account, final String newPassword) { + final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + packet.setTo(account.getServer()); + final Element query = packet.addChild("query", Xmlns.REGISTER); + final Jid jid = account.getJid(); + query.addChild("username").setContent(jid.getLocalpart()); + query.addChild("password").setContent(newPassword); + return packet; + } + + public IqPacket changeAffiliation(Conversation conference, Jid jid, String affiliation) { + List jids = new ArrayList<>(); + jids.add(jid); + return changeAffiliation(conference, jids, affiliation); + } + + public IqPacket changeAffiliation(Conversation conference, List jids, String affiliation) { + IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + packet.setTo(conference.getJid().toBareJid()); + packet.setFrom(conference.getAccount().getJid()); + Element query = packet.query("http://jabber.org/protocol/muc#admin"); + for (Jid jid : jids) { + Element item = query.addChild("item"); + item.setAttribute("jid", jid.toString()); + item.setAttribute("affiliation", affiliation); + } + return packet; + } + + public IqPacket changeRole(Conversation conference, String nick, String role) { + IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + packet.setTo(conference.getJid().toBareJid()); + packet.setFrom(conference.getAccount().getJid()); + Element item = packet.query("http://jabber.org/protocol/muc#admin").addChild("item"); + item.setAttribute("nick", nick); + item.setAttribute("role", role); + return packet; + } + + public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file, String mime) { + IqPacket packet = new IqPacket(IqPacket.TYPE.GET); + packet.setTo(host); + Element request = packet.addChild("request", Xmlns.HTTP_UPLOAD); + request.addChild("filename").setContent(file.getName()); + request.addChild("size").setContent(String.valueOf(file.getExpectedSize())); + if (mime != null) { + request.addChild("content-type").setContent(mime); + } + return packet; + } + + public IqPacket generateCreateAccountWithCaptcha(Account account, String id, Data data) { + final IqPacket register = new IqPacket(IqPacket.TYPE.SET); + register.setFrom(account.getJid().toBareJid()); + register.setTo(account.getServer()); + register.setId(id); + Element query = register.query("jabber:iq:register"); + if (data != null) { + query.addChild(data); + } + return register; + } + + public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId) { + IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + packet.setTo(appServer); + Element command = packet.addChild("command", "http://jabber.org/protocol/commands"); + command.setAttribute("node", "register-push-gcm"); + command.setAttribute("action", "execute"); + Data data = new Data(); + data.put("token", token); + data.put("device-id", deviceId); + data.submit(); + command.addChild(data); + return packet; + } + + public IqPacket enablePush(Jid jid, String node, String secret) { + IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + Element enable = packet.addChild("enable", "urn:xmpp:push:0"); + enable.setAttribute("jid", jid.toString()); + enable.setAttribute("node", node); + Data data = new Data(); + data.setFormType("http://jabber.org/protocol/pubsub#publish-options"); + data.put("secret", secret); + data.submit(); + enable.addChild(data); + return packet; + } + + public IqPacket queryAffiliation(Conversation conversation, String affiliation) { + IqPacket packet = new IqPacket(IqPacket.TYPE.GET); + packet.setTo(conversation.getJid().toBareJid()); + packet.query("http://jabber.org/protocol/muc#admin").addChild("item").setAttribute("affiliation", affiliation); + return packet; + } + + public static Bundle defaultRoomConfiguration() { + Bundle options = new Bundle(); + options.putString("muc#roomconfig_persistentroom", "1"); + options.putString("muc#roomconfig_membersonly", "1"); + options.putString("muc#roomconfig_publicroom", "0"); + options.putString("muc#roomconfig_whois", "anyone"); + return options; + } } diff --git a/src/main/java/de/pixart/messenger/generator/MessageGenerator.java b/src/main/java/de/pixart/messenger/generator/MessageGenerator.java index ce0a81a72..bdb4b7837 100644 --- a/src/main/java/de/pixart/messenger/generator/MessageGenerator.java +++ b/src/main/java/de/pixart/messenger/generator/MessageGenerator.java @@ -23,207 +23,207 @@ import de.pixart.messenger.xmpp.jid.Jid; import de.pixart.messenger.xmpp.stanzas.MessagePacket; public class MessageGenerator extends AbstractGenerator { - public static final String OTR_FALLBACK_MESSAGE = "I would like to start a private (OTR encrypted) conversation but your client doesn’t seem to support that"; - private static final String OMEMO_FALLBACK_MESSAGE = "I sent you an OMEMO encrypted message but your client doesn’t seem to support that. Find more information on https://conversations.im/omemo"; - private static final String PGP_FALLBACK_MESSAGE = "I sent you a PGP encrypted message but your client doesn’t seem to support that."; - - public MessageGenerator(XmppConnectionService service) { - super(service); - } - - private MessagePacket preparePacket(Message message) { - Conversation conversation = message.getConversation(); - Account account = conversation.getAccount(); - MessagePacket packet = new MessagePacket(); - if (conversation.getMode() == Conversation.MODE_SINGLE) { - packet.setTo(message.getCounterpart()); - packet.setType(MessagePacket.TYPE_CHAT); - packet.addChild("markable", "urn:xmpp:chat-markers:0"); - if (this.mXmppConnectionService.indicateReceived()) { - packet.addChild("request", "urn:xmpp:receipts"); - } - } else if (message.getType() == Message.TYPE_PRIVATE) { - packet.setTo(message.getCounterpart()); - packet.setType(MessagePacket.TYPE_CHAT); - if (this.mXmppConnectionService.indicateReceived()) { - packet.addChild("request", "urn:xmpp:receipts"); - } - } else { - packet.setTo(message.getCounterpart().toBareJid()); - packet.setType(MessagePacket.TYPE_GROUPCHAT); - } - packet.setFrom(account.getJid()); - packet.setId(message.getUuid()); - if (message.edited()) { - packet.addChild("replace","urn:xmpp:message-correct:0").setAttribute("id",message.getEditedId()); - } - return packet; - } - - public void addDelay(MessagePacket packet, long timestamp) { - final SimpleDateFormat mDateFormat = new SimpleDateFormat( - "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US); - mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - Element delay = packet.addChild("delay", "urn:xmpp:delay"); - Date date = new Date(timestamp); - delay.setAttribute("stamp", mDateFormat.format(date)); - } - - public MessagePacket generateAxolotlChat(Message message, XmppAxolotlMessage axolotlMessage) { - MessagePacket packet = preparePacket(message); - if (axolotlMessage == null) { - return null; - } - packet.setAxolotlMessage(axolotlMessage.toElement()); - if (Config.supportUnencrypted() && !recipientSupportsOmemo(message)) { - packet.setBody(OMEMO_FALLBACK_MESSAGE); - } - packet.addChild("store", "urn:xmpp:hints"); - return packet; - } - - private static boolean recipientSupportsOmemo(Message message) { - Contact c = message.getContact(); - return c != null && c.getPresences().allOrNonSupport(AxolotlService.PEP_DEVICE_LIST_NOTIFY); - } - - public static void addMessageHints(MessagePacket packet) { - packet.addChild("private", "urn:xmpp:carbons:2"); - packet.addChild("no-copy", "urn:xmpp:hints"); - packet.addChild("no-permanent-store", "urn:xmpp:hints"); - packet.addChild("no-permanent-storage", "urn:xmpp:hints"); //do not copy this. this is wrong. it is *store* - } - - public MessagePacket generateOtrChat(Message message) { - Session otrSession = message.getConversation().getOtrSession(); - if (otrSession == null) { - return null; - } - MessagePacket packet = preparePacket(message); - addMessageHints(packet); - try { - String content; - if (message.hasFileOnRemoteHost()) { - content = message.getFileParams().url.toString(); - } else { - content = message.getBody(); - } - packet.setBody(otrSession.transformSending(content)[0]); - return packet; - } catch (OtrException e) { - return null; - } - } - - public MessagePacket generateChat(Message message) { - MessagePacket packet = preparePacket(message); - String content; - if (message.hasFileOnRemoteHost()) { - Message.FileParams fileParams = message.getFileParams(); - content = fileParams.url.toString(); - packet.addChild("x","jabber:x:oob").addChild("url").setContent(content); - } else { - content = message.getBody(); - } - packet.setBody(content); - return packet; - } - - public MessagePacket generatePgpChat(Message message) { - MessagePacket packet = preparePacket(message); - if (Config.supportUnencrypted()) { - packet.setBody(PGP_FALLBACK_MESSAGE); - } - if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { - packet.addChild("x", "jabber:x:encrypted").setContent(message.getEncryptedBody()); - } else if (message.getEncryption() == Message.ENCRYPTION_PGP) { - packet.addChild("x", "jabber:x:encrypted").setContent(message.getBody()); - } - return packet; - } - - public MessagePacket generateChatState(Conversation conversation) { - final Account account = conversation.getAccount(); - MessagePacket packet = new MessagePacket(); - packet.setType(MessagePacket.TYPE_CHAT); - packet.setTo(conversation.getJid().toBareJid()); - packet.setFrom(account.getJid()); - packet.addChild(ChatState.toElement(conversation.getOutgoingChatState())); - packet.addChild("no-store", "urn:xmpp:hints"); - packet.addChild("no-storage", "urn:xmpp:hints"); //wrong! don't copy this. Its *store* - return packet; - } - - public MessagePacket confirm(final Account account, final Jid to, final String id) { - MessagePacket packet = new MessagePacket(); - packet.setType(MessagePacket.TYPE_CHAT); - packet.setTo(to); - packet.setFrom(account.getJid()); - Element received = packet.addChild("displayed","urn:xmpp:chat-markers:0"); - received.setAttribute("id", id); - packet.addChild("store", "urn:xmpp:hints"); - return packet; - } - - public MessagePacket conferenceSubject(Conversation conversation,String subject) { - MessagePacket packet = new MessagePacket(); - packet.setType(MessagePacket.TYPE_GROUPCHAT); - packet.setTo(conversation.getJid().toBareJid()); - Element subjectChild = new Element("subject"); - subjectChild.setContent(subject); - packet.addChild(subjectChild); - packet.setFrom(conversation.getAccount().getJid().toBareJid()); - return packet; - } - - public MessagePacket directInvite(final Conversation conversation, final Jid contact) { - MessagePacket packet = new MessagePacket(); - packet.setType(MessagePacket.TYPE_NORMAL); - packet.setTo(contact); - packet.setFrom(conversation.getAccount().getJid()); - Element x = packet.addChild("x", "jabber:x:conference"); - x.setAttribute("jid", conversation.getJid().toBareJid().toString()); + public static final String OTR_FALLBACK_MESSAGE = "I would like to start a private (OTR encrypted) conversation but your client doesn’t seem to support that"; + private static final String OMEMO_FALLBACK_MESSAGE = "I sent you an OMEMO encrypted message but your client doesn’t seem to support that. Find more information on https://conversations.im/omemo"; + private static final String PGP_FALLBACK_MESSAGE = "I sent you a PGP encrypted message but your client doesn’t seem to support that."; + + public MessageGenerator(XmppConnectionService service) { + super(service); + } + + private MessagePacket preparePacket(Message message) { + Conversation conversation = message.getConversation(); + Account account = conversation.getAccount(); + MessagePacket packet = new MessagePacket(); + if (conversation.getMode() == Conversation.MODE_SINGLE) { + packet.setTo(message.getCounterpart()); + packet.setType(MessagePacket.TYPE_CHAT); + packet.addChild("markable", "urn:xmpp:chat-markers:0"); + if (this.mXmppConnectionService.indicateReceived()) { + packet.addChild("request", "urn:xmpp:receipts"); + } + } else if (message.getType() == Message.TYPE_PRIVATE) { + packet.setTo(message.getCounterpart()); + packet.setType(MessagePacket.TYPE_CHAT); + if (this.mXmppConnectionService.indicateReceived()) { + packet.addChild("request", "urn:xmpp:receipts"); + } + } else { + packet.setTo(message.getCounterpart().toBareJid()); + packet.setType(MessagePacket.TYPE_GROUPCHAT); + } + packet.setFrom(account.getJid()); + packet.setId(message.getUuid()); + if (message.edited()) { + packet.addChild("replace", "urn:xmpp:message-correct:0").setAttribute("id", message.getEditedId()); + } + return packet; + } + + public void addDelay(MessagePacket packet, long timestamp) { + final SimpleDateFormat mDateFormat = new SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US); + mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + Element delay = packet.addChild("delay", "urn:xmpp:delay"); + Date date = new Date(timestamp); + delay.setAttribute("stamp", mDateFormat.format(date)); + } + + public MessagePacket generateAxolotlChat(Message message, XmppAxolotlMessage axolotlMessage) { + MessagePacket packet = preparePacket(message); + if (axolotlMessage == null) { + return null; + } + packet.setAxolotlMessage(axolotlMessage.toElement()); + if (Config.supportUnencrypted() && !recipientSupportsOmemo(message)) { + packet.setBody(OMEMO_FALLBACK_MESSAGE); + } + packet.addChild("store", "urn:xmpp:hints"); + return packet; + } + + private static boolean recipientSupportsOmemo(Message message) { + Contact c = message.getContact(); + return c != null && c.getPresences().allOrNonSupport(AxolotlService.PEP_DEVICE_LIST_NOTIFY); + } + + public static void addMessageHints(MessagePacket packet) { + packet.addChild("private", "urn:xmpp:carbons:2"); + packet.addChild("no-copy", "urn:xmpp:hints"); + packet.addChild("no-permanent-store", "urn:xmpp:hints"); + packet.addChild("no-permanent-storage", "urn:xmpp:hints"); //do not copy this. this is wrong. it is *store* + } + + public MessagePacket generateOtrChat(Message message) { + Session otrSession = message.getConversation().getOtrSession(); + if (otrSession == null) { + return null; + } + MessagePacket packet = preparePacket(message); + addMessageHints(packet); + try { + String content; + if (message.hasFileOnRemoteHost()) { + content = message.getFileParams().url.toString(); + } else { + content = message.getBody(); + } + packet.setBody(otrSession.transformSending(content)[0]); + return packet; + } catch (OtrException e) { + return null; + } + } + + public MessagePacket generateChat(Message message) { + MessagePacket packet = preparePacket(message); + String content; + if (message.hasFileOnRemoteHost()) { + Message.FileParams fileParams = message.getFileParams(); + content = fileParams.url.toString(); + packet.addChild("x", "jabber:x:oob").addChild("url").setContent(content); + } else { + content = message.getBody(); + } + packet.setBody(content); + return packet; + } + + public MessagePacket generatePgpChat(Message message) { + MessagePacket packet = preparePacket(message); + if (Config.supportUnencrypted()) { + packet.setBody(PGP_FALLBACK_MESSAGE); + } + if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { + packet.addChild("x", "jabber:x:encrypted").setContent(message.getEncryptedBody()); + } else if (message.getEncryption() == Message.ENCRYPTION_PGP) { + packet.addChild("x", "jabber:x:encrypted").setContent(message.getBody()); + } + return packet; + } + + public MessagePacket generateChatState(Conversation conversation) { + final Account account = conversation.getAccount(); + MessagePacket packet = new MessagePacket(); + packet.setType(MessagePacket.TYPE_CHAT); + packet.setTo(conversation.getJid().toBareJid()); + packet.setFrom(account.getJid()); + packet.addChild(ChatState.toElement(conversation.getOutgoingChatState())); + packet.addChild("no-store", "urn:xmpp:hints"); + packet.addChild("no-storage", "urn:xmpp:hints"); //wrong! don't copy this. Its *store* + return packet; + } + + public MessagePacket confirm(final Account account, final Jid to, final String id) { + MessagePacket packet = new MessagePacket(); + packet.setType(MessagePacket.TYPE_CHAT); + packet.setTo(to); + packet.setFrom(account.getJid()); + Element received = packet.addChild("displayed", "urn:xmpp:chat-markers:0"); + received.setAttribute("id", id); + packet.addChild("store", "urn:xmpp:hints"); + return packet; + } + + public MessagePacket conferenceSubject(Conversation conversation, String subject) { + MessagePacket packet = new MessagePacket(); + packet.setType(MessagePacket.TYPE_GROUPCHAT); + packet.setTo(conversation.getJid().toBareJid()); + Element subjectChild = new Element("subject"); + subjectChild.setContent(subject); + packet.addChild(subjectChild); + packet.setFrom(conversation.getAccount().getJid().toBareJid()); + return packet; + } + + public MessagePacket directInvite(final Conversation conversation, final Jid contact) { + MessagePacket packet = new MessagePacket(); + packet.setType(MessagePacket.TYPE_NORMAL); + packet.setTo(contact); + packet.setFrom(conversation.getAccount().getJid()); + Element x = packet.addChild("x", "jabber:x:conference"); + x.setAttribute("jid", conversation.getJid().toBareJid().toString()); String password = conversation.getMucOptions().getPassword(); if (password != null) { - x.setAttribute("password",password); + x.setAttribute("password", password); + } + return packet; + } + + public MessagePacket invite(Conversation conversation, Jid contact) { + MessagePacket packet = new MessagePacket(); + packet.setTo(conversation.getJid().toBareJid()); + packet.setFrom(conversation.getAccount().getJid()); + Element x = new Element("x"); + x.setAttribute("xmlns", "http://jabber.org/protocol/muc#user"); + Element invite = new Element("invite"); + invite.setAttribute("to", contact.toBareJid().toString()); + x.addChild(invite); + packet.addChild(x); + return packet; + } + + public MessagePacket received(Account account, MessagePacket originalMessage, ArrayList namespaces, int type) { + MessagePacket receivedPacket = new MessagePacket(); + receivedPacket.setType(type); + receivedPacket.setTo(originalMessage.getFrom()); + receivedPacket.setFrom(account.getJid()); + for (String namespace : namespaces) { + receivedPacket.addChild("received", namespace).setAttribute("id", originalMessage.getId()); } - return packet; - } - - public MessagePacket invite(Conversation conversation, Jid contact) { - MessagePacket packet = new MessagePacket(); - packet.setTo(conversation.getJid().toBareJid()); - packet.setFrom(conversation.getAccount().getJid()); - Element x = new Element("x"); - x.setAttribute("xmlns", "http://jabber.org/protocol/muc#user"); - Element invite = new Element("invite"); - invite.setAttribute("to", contact.toBareJid().toString()); - x.addChild(invite); - packet.addChild(x); - return packet; - } - - public MessagePacket received(Account account, MessagePacket originalMessage, ArrayList namespaces, int type) { - MessagePacket receivedPacket = new MessagePacket(); - receivedPacket.setType(type); - receivedPacket.setTo(originalMessage.getFrom()); - receivedPacket.setFrom(account.getJid()); - for(String namespace : namespaces) { - receivedPacket.addChild("received", namespace).setAttribute("id", originalMessage.getId()); - } - return receivedPacket; - } - - public MessagePacket generateOtrError(Jid to, String id, String errorText) { - MessagePacket packet = new MessagePacket(); - packet.setType(MessagePacket.TYPE_ERROR); - packet.setAttribute("id",id); - packet.setTo(to); - Element error = packet.addChild("error"); - error.setAttribute("code","406"); - error.setAttribute("type","modify"); - error.addChild("not-acceptable","urn:ietf:params:xml:ns:xmpp-stanzas"); - error.addChild("text").setContent("?OTR Error:" + errorText); - return packet; - } + return receivedPacket; + } + + public MessagePacket generateOtrError(Jid to, String id, String errorText) { + MessagePacket packet = new MessagePacket(); + packet.setType(MessagePacket.TYPE_ERROR); + packet.setAttribute("id", id); + packet.setTo(to); + Element error = packet.addChild("error"); + error.setAttribute("code", "406"); + error.setAttribute("type", "modify"); + error.addChild("not-acceptable", "urn:ietf:params:xml:ns:xmpp-stanzas"); + error.addChild("text").setContent("?OTR Error:" + errorText); + return packet; + } } diff --git a/src/main/java/de/pixart/messenger/generator/PresenceGenerator.java b/src/main/java/de/pixart/messenger/generator/PresenceGenerator.java index bf84444a5..2234d54e6 100644 --- a/src/main/java/de/pixart/messenger/generator/PresenceGenerator.java +++ b/src/main/java/de/pixart/messenger/generator/PresenceGenerator.java @@ -9,63 +9,63 @@ import de.pixart.messenger.xmpp.stanzas.PresencePacket; public class PresenceGenerator extends AbstractGenerator { - public PresenceGenerator(XmppConnectionService service) { - super(service); - } + public PresenceGenerator(XmppConnectionService service) { + super(service); + } - private PresencePacket subscription(String type, Contact contact) { - PresencePacket packet = new PresencePacket(); - packet.setAttribute("type", type); - packet.setTo(contact.getJid()); - packet.setFrom(contact.getAccount().getJid().toBareJid()); - return packet; - } + private PresencePacket subscription(String type, Contact contact) { + PresencePacket packet = new PresencePacket(); + packet.setAttribute("type", type); + packet.setTo(contact.getJid()); + packet.setFrom(contact.getAccount().getJid().toBareJid()); + return packet; + } - public PresencePacket requestPresenceUpdatesFrom(Contact contact) { - return subscription("subscribe", contact); - } + public PresencePacket requestPresenceUpdatesFrom(Contact contact) { + return subscription("subscribe", contact); + } - public PresencePacket stopPresenceUpdatesFrom(Contact contact) { - return subscription("unsubscribe", contact); - } + public PresencePacket stopPresenceUpdatesFrom(Contact contact) { + return subscription("unsubscribe", contact); + } - public PresencePacket stopPresenceUpdatesTo(Contact contact) { - return subscription("unsubscribed", contact); - } + public PresencePacket stopPresenceUpdatesTo(Contact contact) { + return subscription("unsubscribed", contact); + } - public PresencePacket sendPresenceUpdatesTo(Contact contact) { - return subscription("subscribed", contact); - } + public PresencePacket sendPresenceUpdatesTo(Contact contact) { + return subscription("subscribed", contact); + } - public PresencePacket selfPresence(Account account, Presence.Status status) { + public PresencePacket selfPresence(Account account, Presence.Status status) { return selfPresence(account, status, true); } public PresencePacket selfPresence(Account account, Presence.Status status, boolean includePgpAnnouncement) { - PresencePacket packet = new PresencePacket(); - if(status.toShowString() != null) { - packet.addChild("show").setContent(status.toShowString()); - } - packet.setFrom(account.getJid()); - String sig = account.getPgpSignature(); + PresencePacket packet = new PresencePacket(); + if (status.toShowString() != null) { + packet.addChild("show").setContent(status.toShowString()); + } + packet.setFrom(account.getJid()); + String sig = account.getPgpSignature(); if (includePgpAnnouncement && sig != null && mXmppConnectionService.getPgpEngine() != null) { - packet.addChild("x", "jabber:x:signed").setContent(sig); - } - String capHash = getCapHash(); - if (capHash != null) { - Element cap = packet.addChild("c", - "http://jabber.org/protocol/caps"); - cap.setAttribute("hash", "sha-1"); - cap.setAttribute("node", "http://conversations.im"); - cap.setAttribute("ver", capHash); - } - return packet; - } + packet.addChild("x", "jabber:x:signed").setContent(sig); + } + String capHash = getCapHash(); + if (capHash != null) { + Element cap = packet.addChild("c", + "http://jabber.org/protocol/caps"); + cap.setAttribute("hash", "sha-1"); + cap.setAttribute("node", "http://conversations.im"); + cap.setAttribute("ver", capHash); + } + return packet; + } - public PresencePacket sendOfflinePresence(Account account) { - PresencePacket packet = new PresencePacket(); - packet.setFrom(account.getJid()); - packet.setAttribute("type","unavailable"); - return packet; - } + public PresencePacket sendOfflinePresence(Account account) { + PresencePacket packet = new PresencePacket(); + packet.setFrom(account.getJid()); + packet.setAttribute("type", "unavailable"); + return packet; + } } diff --git a/src/main/java/de/pixart/messenger/http/HttpConnectionManager.java b/src/main/java/de/pixart/messenger/http/HttpConnectionManager.java index 921b20571..a03e36991 100644 --- a/src/main/java/de/pixart/messenger/http/HttpConnectionManager.java +++ b/src/main/java/de/pixart/messenger/http/HttpConnectionManager.java @@ -25,75 +25,75 @@ import de.pixart.messenger.utils.SSLSocketHelper; public class HttpConnectionManager extends AbstractConnectionManager { - public HttpConnectionManager(XmppConnectionService service) { - super(service); - } - - private List downloadConnections = new CopyOnWriteArrayList<>(); - private List uploadConnections = new CopyOnWriteArrayList<>(); - - public HttpDownloadConnection createNewDownloadConnection(Message message) { - return this.createNewDownloadConnection(message, false); - } - - public HttpDownloadConnection createNewDownloadConnection(Message message, boolean interactive) { - HttpDownloadConnection connection = new HttpDownloadConnection(this); - connection.init(message,interactive); - this.downloadConnections.add(connection); - return connection; - } - - public HttpUploadConnection createNewUploadConnection(Message message, boolean delay) { - HttpUploadConnection connection = new HttpUploadConnection(this); - connection.init(message,delay); - this.uploadConnections.add(connection); - return connection; - } - - public void finishConnection(HttpDownloadConnection connection) { - this.downloadConnections.remove(connection); - } - - public void finishUploadConnection(HttpUploadConnection httpUploadConnection) { - this.uploadConnections.remove(httpUploadConnection); - } - - public void setupTrustManager(final HttpsURLConnection connection, final boolean interactive) { - final X509TrustManager trustManager; - final HostnameVerifier hostnameVerifier; - if (interactive) { - trustManager = mXmppConnectionService.getMemorizingTrustManager(); - hostnameVerifier = mXmppConnectionService - .getMemorizingTrustManager().wrapHostnameVerifier( - new StrictHostnameVerifier()); - } else { - trustManager = mXmppConnectionService.getMemorizingTrustManager() - .getNonInteractive(); - hostnameVerifier = mXmppConnectionService - .getMemorizingTrustManager() - .wrapHostnameVerifierNonInteractive( - new StrictHostnameVerifier()); - } - try { - final SSLContext sc = SSLSocketHelper.getSSLContext(); - sc.init(null, new X509TrustManager[]{trustManager}, - mXmppConnectionService.getRNG()); - - final SSLSocketFactory sf = sc.getSocketFactory(); - final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites( - sf.getSupportedCipherSuites()); - if (cipherSuites.length > 0) { - sc.getDefaultSSLParameters().setCipherSuites(cipherSuites); - - } - - connection.setSSLSocketFactory(sf); - connection.setHostnameVerifier(hostnameVerifier); - } catch (final KeyManagementException | NoSuchAlgorithmException ignored) { - } - } - - public Proxy getProxy() throws IOException { - return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(InetAddress.getByAddress(new byte[]{127,0,0,1}), 8118)); - } + public HttpConnectionManager(XmppConnectionService service) { + super(service); + } + + private List downloadConnections = new CopyOnWriteArrayList<>(); + private List uploadConnections = new CopyOnWriteArrayList<>(); + + public HttpDownloadConnection createNewDownloadConnection(Message message) { + return this.createNewDownloadConnection(message, false); + } + + public HttpDownloadConnection createNewDownloadConnection(Message message, boolean interactive) { + HttpDownloadConnection connection = new HttpDownloadConnection(this); + connection.init(message, interactive); + this.downloadConnections.add(connection); + return connection; + } + + public HttpUploadConnection createNewUploadConnection(Message message, boolean delay) { + HttpUploadConnection connection = new HttpUploadConnection(this); + connection.init(message, delay); + this.uploadConnections.add(connection); + return connection; + } + + public void finishConnection(HttpDownloadConnection connection) { + this.downloadConnections.remove(connection); + } + + public void finishUploadConnection(HttpUploadConnection httpUploadConnection) { + this.uploadConnections.remove(httpUploadConnection); + } + + public void setupTrustManager(final HttpsURLConnection connection, final boolean interactive) { + final X509TrustManager trustManager; + final HostnameVerifier hostnameVerifier; + if (interactive) { + trustManager = mXmppConnectionService.getMemorizingTrustManager(); + hostnameVerifier = mXmppConnectionService + .getMemorizingTrustManager().wrapHostnameVerifier( + new StrictHostnameVerifier()); + } else { + trustManager = mXmppConnectionService.getMemorizingTrustManager() + .getNonInteractive(); + hostnameVerifier = mXmppConnectionService + .getMemorizingTrustManager() + .wrapHostnameVerifierNonInteractive( + new StrictHostnameVerifier()); + } + try { + final SSLContext sc = SSLSocketHelper.getSSLContext(); + sc.init(null, new X509TrustManager[]{trustManager}, + mXmppConnectionService.getRNG()); + + final SSLSocketFactory sf = sc.getSocketFactory(); + final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites( + sf.getSupportedCipherSuites()); + if (cipherSuites.length > 0) { + sc.getDefaultSSLParameters().setCipherSuites(cipherSuites); + + } + + connection.setSSLSocketFactory(sf); + connection.setHostnameVerifier(hostnameVerifier); + } catch (final KeyManagementException | NoSuchAlgorithmException ignored) { + } + } + + public Proxy getProxy() throws IOException { + return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(InetAddress.getByAddress(new byte[]{127, 0, 0, 1}), 8118)); + } } diff --git a/src/main/java/de/pixart/messenger/http/HttpDownloadConnection.java b/src/main/java/de/pixart/messenger/http/HttpDownloadConnection.java index f091c9dad..8c342e6f2 100644 --- a/src/main/java/de/pixart/messenger/http/HttpDownloadConnection.java +++ b/src/main/java/de/pixart/messenger/http/HttpDownloadConnection.java @@ -32,335 +32,335 @@ import de.pixart.messenger.utils.FileWriterException; public class HttpDownloadConnection implements Transferable { - private HttpConnectionManager mHttpConnectionManager; - private XmppConnectionService mXmppConnectionService; - - private URL mUrl; - private Message message; - private DownloadableFile file; - private int mStatus = Transferable.STATUS_UNKNOWN; - private boolean acceptedAutomatically = false; - private int mProgress = 0; - private boolean mUseTor = false; - private boolean canceled = false; - - private final SimpleDateFormat fileDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US); - - public HttpDownloadConnection(HttpConnectionManager manager) { - this.mHttpConnectionManager = manager; - this.mXmppConnectionService = manager.getXmppConnectionService(); - this.mUseTor = mXmppConnectionService.useTorToConnect(); - } - - @Override - public boolean start() { - if (mXmppConnectionService.hasInternetConnection()) { - if (this.mStatus == STATUS_OFFER_CHECK_FILESIZE) { - checkFileSize(true); - } else { - new Thread(new FileDownloader(true)).start(); - } - return true; - } else { - return false; - } - } - - public void init(Message message) { - init(message, false); - } - - public void init(Message message, boolean interactive) { - this.message = message; - this.message.setTransferable(this); - try { - if (message.hasFileOnRemoteHost()) { - mUrl = message.getFileParams().url; - } else { - mUrl = new URL(message.getBody()); - } - String[] parts = mUrl.getPath().toLowerCase().split("\\."); - String lastPart = parts.length >= 1 ? parts[parts.length - 1] : null; - String secondToLast = parts.length >= 2 ? parts[parts.length -2] : null; - if ("pgp".equals(lastPart) || "gpg".equals(lastPart)) { - this.message.setEncryption(Message.ENCRYPTION_PGP); - } else if (message.getEncryption() != Message.ENCRYPTION_OTR - && message.getEncryption() != Message.ENCRYPTION_AXOLOTL) { - this.message.setEncryption(Message.ENCRYPTION_NONE); - } - String extension; - if (VALID_CRYPTO_EXTENSIONS.contains(lastPart)) { - extension = secondToLast; - } else { - extension = lastPart; - } - String filename = fileDateFormat.format(new Date(message.getTimeSent()))+"_"+message.getUuid().substring(0,4); - message.setRelativeFilePath(filename + "." + extension); - this.file = mXmppConnectionService.getFileBackend().getFile(message, false); - String reference = mUrl.getRef(); - if (reference != null && reference.length() == 96) { - this.file.setKeyAndIv(CryptoHelper.hexToBytes(reference)); - } - - if ((this.message.getEncryption() == Message.ENCRYPTION_OTR - || this.message.getEncryption() == Message.ENCRYPTION_AXOLOTL) - && this.file.getKey() == null) { - this.message.setEncryption(Message.ENCRYPTION_NONE); - } - checkFileSize(interactive); - } catch (MalformedURLException e) { - this.cancel(); - } - } - - private void checkFileSize(boolean interactive) { - new Thread(new FileSizeChecker(interactive)).start(); - } - - @Override - public void cancel() { - this.canceled = true; - mHttpConnectionManager.finishConnection(this); - if (message.isFileOrImage()) { - message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); - } else { - message.setTransferable(null); - } - mXmppConnectionService.updateConversationUi(); - } - - private void finish() { - mXmppConnectionService.getFileBackend().updateMediaScanner(file); - message.setTransferable(null); - mHttpConnectionManager.finishConnection(this); - boolean notify = acceptedAutomatically && !message.isRead(); - if (message.getEncryption() == Message.ENCRYPTION_PGP) { - notify = message.getConversation().getAccount().getPgpDecryptionService().decrypt(message, notify); - } - mXmppConnectionService.updateConversationUi(); - if (notify) { - mXmppConnectionService.getNotificationService().push(message); - } - } - - private void changeStatus(int status) { - this.mStatus = status; - mXmppConnectionService.updateConversationUi(); - } - - private void showToastForException(Exception e) { - if (e instanceof java.net.UnknownHostException) { - mXmppConnectionService.showErrorToastInUi(R.string.download_failed_server_not_found); - } else if (e instanceof java.net.ConnectException) { - mXmppConnectionService.showErrorToastInUi(R.string.download_failed_could_not_connect); - } else if (e instanceof FileWriterException) { - mXmppConnectionService.showErrorToastInUi(R.string.download_failed_could_not_write_file); - } else if (!(e instanceof CancellationException)) { - mXmppConnectionService.showErrorToastInUi(R.string.download_failed_file_not_found); - } - } - - private class FileSizeChecker implements Runnable { - - private boolean interactive = false; - - public FileSizeChecker(boolean interactive) { - this.interactive = interactive; - } - - @Override - public void run() { - long size; - try { - size = retrieveFileSize(); - } catch (Exception e) { - changeStatus(STATUS_OFFER_CHECK_FILESIZE); - Log.d(Config.LOGTAG, "io exception in http file size checker: " + e.getMessage()); - if (interactive) { - showToastForException(e); - } else { - HttpDownloadConnection.this.acceptedAutomatically = false; - HttpDownloadConnection.this.mXmppConnectionService.getNotificationService().push(message); - } - cancel(); - return; - } - file.setExpectedSize(size); - if (mHttpConnectionManager.hasStoragePermission() - && size <= mHttpConnectionManager.getAutoAcceptFileSize() - && mXmppConnectionService.isDataSaverDisabled()) { - HttpDownloadConnection.this.acceptedAutomatically = true; - new Thread(new FileDownloader(interactive)).start(); - } else { - changeStatus(STATUS_OFFER); - HttpDownloadConnection.this.acceptedAutomatically = false; - HttpDownloadConnection.this.mXmppConnectionService.getNotificationService().push(message); - } - } - - private long retrieveFileSize() throws IOException { - PowerManager.WakeLock wakeLock = mHttpConnectionManager.createWakeLock("http_download_"+message.getUuid()); - try { - wakeLock.acquire(); - Log.d(Config.LOGTAG, "retrieve file size. interactive:" + String.valueOf(interactive)); - changeStatus(STATUS_CHECKING); - HttpURLConnection connection; - if (mUseTor) { - connection = (HttpURLConnection) mUrl.openConnection(mHttpConnectionManager.getProxy()); - } else { - connection = (HttpURLConnection) mUrl.openConnection(); - } - connection.setRequestMethod("HEAD"); - Log.d(Config.LOGTAG,"url: "+connection.getURL().toString()); - Log.d(Config.LOGTAG,"connection: "+connection.toString()); - connection.setRequestProperty("User-Agent", mXmppConnectionService.getIqGenerator().getIdentityName()); - if (connection instanceof HttpsURLConnection) { - mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive); - } - connection.setConnectTimeout(Config.SOCKET_TIMEOUT * 1000); - connection.setReadTimeout(Config.CONNECT_TIMEOUT * 1000); - connection.connect(); - String contentLength = connection.getHeaderField("Content-Length"); - connection.disconnect(); - if (contentLength == null) { - throw new IOException("no content-length found in HEAD response"); - } - wakeLock.release(); - return Long.parseLong(contentLength, 10); - } catch (IOException e) { - throw e; - } catch (NumberFormatException e) { - throw new IOException(); - } - } - - } - - private class FileDownloader implements Runnable { - - private boolean interactive = false; - - private OutputStream os; - - public FileDownloader(boolean interactive) { - this.interactive = interactive; - } - - @Override - public void run() { - try { - changeStatus(STATUS_DOWNLOADING); - download(); - updateImageBounds(); - finish(); - } catch (SSLHandshakeException e) { - changeStatus(STATUS_OFFER); - } catch (Exception e) { - if (interactive) { - showToastForException(e); - } else { - HttpDownloadConnection.this.acceptedAutomatically = false; - HttpDownloadConnection.this.mXmppConnectionService.getNotificationService().push(message); - } - cancel(); - } - } - - private void download() throws Exception { - InputStream is = null; - PowerManager.WakeLock wakeLock = mHttpConnectionManager.createWakeLock("http_download_"+message.getUuid()); - try { - wakeLock.acquire(); - HttpURLConnection connection; - if (mUseTor) { - connection = (HttpURLConnection) mUrl.openConnection(mHttpConnectionManager.getProxy()); - } else { - connection = (HttpURLConnection) mUrl.openConnection(); - } - if (connection instanceof HttpsURLConnection) { - mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive); - } - connection.setRequestProperty("User-Agent",mXmppConnectionService.getIqGenerator().getIdentityName()); - final boolean tryResume = file.exists() && file.getKey() == null; - if (tryResume) { - Log.d(Config.LOGTAG,"http download trying resume"); - long size = file.getSize(); - connection.setRequestProperty("Range", "bytes="+size+"-"); - } - connection.setConnectTimeout(Config.SOCKET_TIMEOUT * 1000); - connection.setReadTimeout(Config.CONNECT_TIMEOUT * 1000); - connection.connect(); - is = new BufferedInputStream(connection.getInputStream()); - boolean serverResumed = "bytes".equals(connection.getHeaderField("Accept-Ranges")); - long transmitted = 0; - long expected = file.getExpectedSize(); - if (tryResume && serverResumed) { - Log.d(Config.LOGTAG,"server resumed"); - transmitted = file.getSize(); - updateProgress((int) ((((double) transmitted) / expected) * 100)); - os = AbstractConnectionManager.createAppendedOutputStream(file); - } else { - file.getParentFile().mkdirs(); - file.createNewFile(); - os = AbstractConnectionManager.createOutputStream(file, true); - } - int count; - byte[] buffer = new byte[1024]; - while ((count = is.read(buffer)) != -1) { - transmitted += count; - try { - os.write(buffer, 0, count); - } catch (IOException e) { - throw new FileWriterException(); - } - updateProgress((int) ((((double) transmitted) / expected) * 100)); - if (canceled) { - throw new CancellationException(); - } - } - try { - os.flush(); - } catch (IOException e) { - throw new FileWriterException(); - } - } catch (CancellationException | IOException e) { - throw e; - } finally { - FileBackend.close(os); - FileBackend.close(is); - wakeLock.release(); - } - } - - private void updateImageBounds() { - message.setType(Message.TYPE_FILE); - mXmppConnectionService.getFileBackend().updateFileParams(message, mUrl); - mXmppConnectionService.updateMessage(message); - } - - } - - public void updateProgress(int i) { - this.mProgress = i; - mXmppConnectionService.updateConversationUi(); - } - - @Override - public int getStatus() { - return this.mStatus; - } - - @Override - public long getFileSize() { - if (this.file != null) { - return this.file.getExpectedSize(); - } else { - return 0; - } - } - - @Override - public int getProgress() { - return this.mProgress; - } + private HttpConnectionManager mHttpConnectionManager; + private XmppConnectionService mXmppConnectionService; + + private URL mUrl; + private Message message; + private DownloadableFile file; + private int mStatus = Transferable.STATUS_UNKNOWN; + private boolean acceptedAutomatically = false; + private int mProgress = 0; + private boolean mUseTor = false; + private boolean canceled = false; + + private final SimpleDateFormat fileDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US); + + public HttpDownloadConnection(HttpConnectionManager manager) { + this.mHttpConnectionManager = manager; + this.mXmppConnectionService = manager.getXmppConnectionService(); + this.mUseTor = mXmppConnectionService.useTorToConnect(); + } + + @Override + public boolean start() { + if (mXmppConnectionService.hasInternetConnection()) { + if (this.mStatus == STATUS_OFFER_CHECK_FILESIZE) { + checkFileSize(true); + } else { + new Thread(new FileDownloader(true)).start(); + } + return true; + } else { + return false; + } + } + + public void init(Message message) { + init(message, false); + } + + public void init(Message message, boolean interactive) { + this.message = message; + this.message.setTransferable(this); + try { + if (message.hasFileOnRemoteHost()) { + mUrl = message.getFileParams().url; + } else { + mUrl = new URL(message.getBody()); + } + String[] parts = mUrl.getPath().toLowerCase().split("\\."); + String lastPart = parts.length >= 1 ? parts[parts.length - 1] : null; + String secondToLast = parts.length >= 2 ? parts[parts.length - 2] : null; + if ("pgp".equals(lastPart) || "gpg".equals(lastPart)) { + this.message.setEncryption(Message.ENCRYPTION_PGP); + } else if (message.getEncryption() != Message.ENCRYPTION_OTR + && message.getEncryption() != Message.ENCRYPTION_AXOLOTL) { + this.message.setEncryption(Message.ENCRYPTION_NONE); + } + String extension; + if (VALID_CRYPTO_EXTENSIONS.contains(lastPart)) { + extension = secondToLast; + } else { + extension = lastPart; + } + String filename = fileDateFormat.format(new Date(message.getTimeSent())) + "_" + message.getUuid().substring(0, 4); + message.setRelativeFilePath(filename + "." + extension); + this.file = mXmppConnectionService.getFileBackend().getFile(message, false); + String reference = mUrl.getRef(); + if (reference != null && reference.length() == 96) { + this.file.setKeyAndIv(CryptoHelper.hexToBytes(reference)); + } + + if ((this.message.getEncryption() == Message.ENCRYPTION_OTR + || this.message.getEncryption() == Message.ENCRYPTION_AXOLOTL) + && this.file.getKey() == null) { + this.message.setEncryption(Message.ENCRYPTION_NONE); + } + checkFileSize(interactive); + } catch (MalformedURLException e) { + this.cancel(); + } + } + + private void checkFileSize(boolean interactive) { + new Thread(new FileSizeChecker(interactive)).start(); + } + + @Override + public void cancel() { + this.canceled = true; + mHttpConnectionManager.finishConnection(this); + if (message.isFileOrImage()) { + message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); + } else { + message.setTransferable(null); + } + mXmppConnectionService.updateConversationUi(); + } + + private void finish() { + mXmppConnectionService.getFileBackend().updateMediaScanner(file); + message.setTransferable(null); + mHttpConnectionManager.finishConnection(this); + boolean notify = acceptedAutomatically && !message.isRead(); + if (message.getEncryption() == Message.ENCRYPTION_PGP) { + notify = message.getConversation().getAccount().getPgpDecryptionService().decrypt(message, notify); + } + mXmppConnectionService.updateConversationUi(); + if (notify) { + mXmppConnectionService.getNotificationService().push(message); + } + } + + private void changeStatus(int status) { + this.mStatus = status; + mXmppConnectionService.updateConversationUi(); + } + + private void showToastForException(Exception e) { + if (e instanceof java.net.UnknownHostException) { + mXmppConnectionService.showErrorToastInUi(R.string.download_failed_server_not_found); + } else if (e instanceof java.net.ConnectException) { + mXmppConnectionService.showErrorToastInUi(R.string.download_failed_could_not_connect); + } else if (e instanceof FileWriterException) { + mXmppConnectionService.showErrorToastInUi(R.string.download_failed_could_not_write_file); + } else if (!(e instanceof CancellationException)) { + mXmppConnectionService.showErrorToastInUi(R.string.download_failed_file_not_found); + } + } + + private class FileSizeChecker implements Runnable { + + private boolean interactive = false; + + public FileSizeChecker(boolean interactive) { + this.interactive = interactive; + } + + @Override + public void run() { + long size; + try { + size = retrieveFileSize(); + } catch (Exception e) { + changeStatus(STATUS_OFFER_CHECK_FILESIZE); + Log.d(Config.LOGTAG, "io exception in http file size checker: " + e.getMessage()); + if (interactive) { + showToastForException(e); + } else { + HttpDownloadConnection.this.acceptedAutomatically = false; + HttpDownloadConnection.this.mXmppConnectionService.getNotificationService().push(message); + } + cancel(); + return; + } + file.setExpectedSize(size); + if (mHttpConnectionManager.hasStoragePermission() + && size <= mHttpConnectionManager.getAutoAcceptFileSize() + && mXmppConnectionService.isDataSaverDisabled()) { + HttpDownloadConnection.this.acceptedAutomatically = true; + new Thread(new FileDownloader(interactive)).start(); + } else { + changeStatus(STATUS_OFFER); + HttpDownloadConnection.this.acceptedAutomatically = false; + HttpDownloadConnection.this.mXmppConnectionService.getNotificationService().push(message); + } + } + + private long retrieveFileSize() throws IOException { + PowerManager.WakeLock wakeLock = mHttpConnectionManager.createWakeLock("http_download_" + message.getUuid()); + try { + wakeLock.acquire(); + Log.d(Config.LOGTAG, "retrieve file size. interactive:" + String.valueOf(interactive)); + changeStatus(STATUS_CHECKING); + HttpURLConnection connection; + if (mUseTor) { + connection = (HttpURLConnection) mUrl.openConnection(mHttpConnectionManager.getProxy()); + } else { + connection = (HttpURLConnection) mUrl.openConnection(); + } + connection.setRequestMethod("HEAD"); + Log.d(Config.LOGTAG, "url: " + connection.getURL().toString()); + Log.d(Config.LOGTAG, "connection: " + connection.toString()); + connection.setRequestProperty("User-Agent", mXmppConnectionService.getIqGenerator().getIdentityName()); + if (connection instanceof HttpsURLConnection) { + mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive); + } + connection.setConnectTimeout(Config.SOCKET_TIMEOUT * 1000); + connection.setReadTimeout(Config.CONNECT_TIMEOUT * 1000); + connection.connect(); + String contentLength = connection.getHeaderField("Content-Length"); + connection.disconnect(); + if (contentLength == null) { + throw new IOException("no content-length found in HEAD response"); + } + wakeLock.release(); + return Long.parseLong(contentLength, 10); + } catch (IOException e) { + throw e; + } catch (NumberFormatException e) { + throw new IOException(); + } + } + + } + + private class FileDownloader implements Runnable { + + private boolean interactive = false; + + private OutputStream os; + + public FileDownloader(boolean interactive) { + this.interactive = interactive; + } + + @Override + public void run() { + try { + changeStatus(STATUS_DOWNLOADING); + download(); + updateImageBounds(); + finish(); + } catch (SSLHandshakeException e) { + changeStatus(STATUS_OFFER); + } catch (Exception e) { + if (interactive) { + showToastForException(e); + } else { + HttpDownloadConnection.this.acceptedAutomatically = false; + HttpDownloadConnection.this.mXmppConnectionService.getNotificationService().push(message); + } + cancel(); + } + } + + private void download() throws Exception { + InputStream is = null; + PowerManager.WakeLock wakeLock = mHttpConnectionManager.createWakeLock("http_download_" + message.getUuid()); + try { + wakeLock.acquire(); + HttpURLConnection connection; + if (mUseTor) { + connection = (HttpURLConnection) mUrl.openConnection(mHttpConnectionManager.getProxy()); + } else { + connection = (HttpURLConnection) mUrl.openConnection(); + } + if (connection instanceof HttpsURLConnection) { + mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive); + } + connection.setRequestProperty("User-Agent", mXmppConnectionService.getIqGenerator().getIdentityName()); + final boolean tryResume = file.exists() && file.getKey() == null; + if (tryResume) { + Log.d(Config.LOGTAG, "http download trying resume"); + long size = file.getSize(); + connection.setRequestProperty("Range", "bytes=" + size + "-"); + } + connection.setConnectTimeout(Config.SOCKET_TIMEOUT * 1000); + connection.setReadTimeout(Config.CONNECT_TIMEOUT * 1000); + connection.connect(); + is = new BufferedInputStream(connection.getInputStream()); + boolean serverResumed = "bytes".equals(connection.getHeaderField("Accept-Ranges")); + long transmitted = 0; + long expected = file.getExpectedSize(); + if (tryResume && serverResumed) { + Log.d(Config.LOGTAG, "server resumed"); + transmitted = file.getSize(); + updateProgress((int) ((((double) transmitted) / expected) * 100)); + os = AbstractConnectionManager.createAppendedOutputStream(file); + } else { + file.getParentFile().mkdirs(); + file.createNewFile(); + os = AbstractConnectionManager.createOutputStream(file, true); + } + int count; + byte[] buffer = new byte[1024]; + while ((count = is.read(buffer)) != -1) { + transmitted += count; + try { + os.write(buffer, 0, count); + } catch (IOException e) { + throw new FileWriterException(); + } + updateProgress((int) ((((double) transmitted) / expected) * 100)); + if (canceled) { + throw new CancellationException(); + } + } + try { + os.flush(); + } catch (IOException e) { + throw new FileWriterException(); + } + } catch (CancellationException | IOException e) { + throw e; + } finally { + FileBackend.close(os); + FileBackend.close(is); + wakeLock.release(); + } + } + + private void updateImageBounds() { + message.setType(Message.TYPE_FILE); + mXmppConnectionService.getFileBackend().updateFileParams(message, mUrl); + mXmppConnectionService.updateMessage(message); + } + + } + + public void updateProgress(int i) { + this.mProgress = i; + mXmppConnectionService.updateConversationUi(); + } + + @Override + public int getStatus() { + return this.mStatus; + } + + @Override + public long getFileSize() { + if (this.file != null) { + return this.file.getExpectedSize(); + } else { + return 0; + } + } + + @Override + public int getProgress() { + return this.mProgress; + } } diff --git a/src/main/java/de/pixart/messenger/http/HttpUploadConnection.java b/src/main/java/de/pixart/messenger/http/HttpUploadConnection.java index 95f840d67..afb0b04df 100644 --- a/src/main/java/de/pixart/messenger/http/HttpUploadConnection.java +++ b/src/main/java/de/pixart/messenger/http/HttpUploadConnection.java @@ -34,208 +34,208 @@ import de.pixart.messenger.xmpp.stanzas.IqPacket; public class HttpUploadConnection implements Transferable { - private HttpConnectionManager mHttpConnectionManager; - private XmppConnectionService mXmppConnectionService; - - private boolean canceled = false; - private boolean delayed = false; - private Account account; - private DownloadableFile file; - private Message message; - private String mime; - private URL mGetUrl; - private URL mPutUrl; - private boolean mUseTor = false; - - private byte[] key = null; - - private long transmitted = 0; - - private InputStream mFileInputStream; - - public HttpUploadConnection(HttpConnectionManager httpConnectionManager) { - this.mHttpConnectionManager = httpConnectionManager; - this.mXmppConnectionService = httpConnectionManager.getXmppConnectionService(); - this.mUseTor = mXmppConnectionService.useTorToConnect(); - } - - @Override - public boolean start() { - return false; - } - - @Override - public int getStatus() { - return STATUS_UPLOADING; - } - - @Override - public long getFileSize() { - return file == null ? 0 : file.getExpectedSize(); - } - - @Override - public int getProgress() { - if (file == null) { - return 0; - } - return (int) ((((double) transmitted) / file.getExpectedSize()) * 100); - } - - @Override - public void cancel() { - this.canceled = true; - } + private HttpConnectionManager mHttpConnectionManager; + private XmppConnectionService mXmppConnectionService; + + private boolean canceled = false; + private boolean delayed = false; + private Account account; + private DownloadableFile file; + private Message message; + private String mime; + private URL mGetUrl; + private URL mPutUrl; + private boolean mUseTor = false; + + private byte[] key = null; + + private long transmitted = 0; + + private InputStream mFileInputStream; + + public HttpUploadConnection(HttpConnectionManager httpConnectionManager) { + this.mHttpConnectionManager = httpConnectionManager; + this.mXmppConnectionService = httpConnectionManager.getXmppConnectionService(); + this.mUseTor = mXmppConnectionService.useTorToConnect(); + } + + @Override + public boolean start() { + return false; + } + + @Override + public int getStatus() { + return STATUS_UPLOADING; + } + + @Override + public long getFileSize() { + return file == null ? 0 : file.getExpectedSize(); + } + + @Override + public int getProgress() { + if (file == null) { + return 0; + } + return (int) ((((double) transmitted) / file.getExpectedSize()) * 100); + } + + @Override + public void cancel() { + this.canceled = true; + } private void fail(String errorMessage) { - mHttpConnectionManager.finishUploadConnection(this); - message.setTransferable(null); + mHttpConnectionManager.finishUploadConnection(this); + message.setTransferable(null); mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED, errorMessage); FileBackend.close(mFileInputStream); - } - - public void init(Message message, boolean delay) { - this.message = message; - this.account = message.getConversation().getAccount(); - this.file = mXmppConnectionService.getFileBackend().getFile(message, false); - this.mime = this.file.getMimeType(); - this.delayed = delay; - if (Config.ENCRYPT_ON_HTTP_UPLOADED - || message.getEncryption() == Message.ENCRYPTION_AXOLOTL - || message.getEncryption() == Message.ENCRYPTION_OTR) { - this.key = new byte[48]; - mXmppConnectionService.getRNG().nextBytes(this.key); - this.file.setKeyAndIv(this.key); - } - Pair pair; - try { - pair = AbstractConnectionManager.createInputStream(file, true); - } catch (FileNotFoundException e) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not find file to upload - "+e.getMessage()); - fail(e.getMessage()); - return; - } - if (pair != null) { - this.file.setExpectedSize(pair.second); - this.mFileInputStream = pair.first; - } - Jid host = account.getXmppConnection().findDiscoItemByFeature(Xmlns.HTTP_UPLOAD); - IqPacket request = mXmppConnectionService.getIqGenerator().requestHttpUploadSlot(host,file,mime); - mXmppConnectionService.sendIqPacket(account, request, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - Element slot = packet.findChild("slot",Xmlns.HTTP_UPLOAD); - if (slot != null) { - try { - mGetUrl = new URL(slot.findChildContent("get")); - mPutUrl = new URL(slot.findChildContent("put")); - if (!canceled) { - new Thread(new FileUploader()).start(); - } - return; - } catch (MalformedURLException e) { - //fall through - } - } - } - Log.d(Config.LOGTAG,account.getJid().toString()+": invalid response to slot request "+packet); + } + + public void init(Message message, boolean delay) { + this.message = message; + this.account = message.getConversation().getAccount(); + this.file = mXmppConnectionService.getFileBackend().getFile(message, false); + this.mime = this.file.getMimeType(); + this.delayed = delay; + if (Config.ENCRYPT_ON_HTTP_UPLOADED + || message.getEncryption() == Message.ENCRYPTION_AXOLOTL + || message.getEncryption() == Message.ENCRYPTION_OTR) { + this.key = new byte[48]; + mXmppConnectionService.getRNG().nextBytes(this.key); + this.file.setKeyAndIv(this.key); + } + Pair pair; + try { + pair = AbstractConnectionManager.createInputStream(file, true); + } catch (FileNotFoundException e) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not find file to upload - " + e.getMessage()); + fail(e.getMessage()); + return; + } + if (pair != null) { + this.file.setExpectedSize(pair.second); + this.mFileInputStream = pair.first; + } + Jid host = account.getXmppConnection().findDiscoItemByFeature(Xmlns.HTTP_UPLOAD); + IqPacket request = mXmppConnectionService.getIqGenerator().requestHttpUploadSlot(host, file, mime); + mXmppConnectionService.sendIqPacket(account, request, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.RESULT) { + Element slot = packet.findChild("slot", Xmlns.HTTP_UPLOAD); + if (slot != null) { + try { + mGetUrl = new URL(slot.findChildContent("get")); + mPutUrl = new URL(slot.findChildContent("put")); + if (!canceled) { + new Thread(new FileUploader()).start(); + } + return; + } catch (MalformedURLException e) { + //fall through + } + } + } + Log.d(Config.LOGTAG, account.getJid().toString() + ": invalid response to slot request " + packet); fail(IqParser.extractErrorMessage(packet)); - } - }); - message.setTransferable(this); - mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND); - } - - private class FileUploader implements Runnable { - - @Override - public void run() { - this.upload(); - } - - private void upload() { - OutputStream os = null; - HttpURLConnection connection = null; - PowerManager.WakeLock wakeLock = mHttpConnectionManager.createWakeLock("http_upload_"+message.getUuid()); - try { - wakeLock.acquire(); - Log.d(Config.LOGTAG, "uploading to " + mPutUrl.toString()); - if (mUseTor) { - connection = (HttpURLConnection) mPutUrl.openConnection(mHttpConnectionManager.getProxy()); - } else { - connection = (HttpURLConnection) mPutUrl.openConnection(); - } - if (connection instanceof HttpsURLConnection) { - mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, true); - } - connection.setRequestMethod("PUT"); - connection.setFixedLengthStreamingMode((int) file.getExpectedSize()); - connection.setRequestProperty("Content-Type", mime == null ? "application/octet-stream" : mime); - connection.setRequestProperty("User-Agent",mXmppConnectionService.getIqGenerator().getIdentityName()); - connection.setDoOutput(true); + } + }); + message.setTransferable(this); + mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND); + } + + private class FileUploader implements Runnable { + + @Override + public void run() { + this.upload(); + } + + private void upload() { + OutputStream os = null; + HttpURLConnection connection = null; + PowerManager.WakeLock wakeLock = mHttpConnectionManager.createWakeLock("http_upload_" + message.getUuid()); + try { + wakeLock.acquire(); + Log.d(Config.LOGTAG, "uploading to " + mPutUrl.toString()); + if (mUseTor) { + connection = (HttpURLConnection) mPutUrl.openConnection(mHttpConnectionManager.getProxy()); + } else { + connection = (HttpURLConnection) mPutUrl.openConnection(); + } + if (connection instanceof HttpsURLConnection) { + mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, true); + } + connection.setRequestMethod("PUT"); + connection.setFixedLengthStreamingMode((int) file.getExpectedSize()); + connection.setRequestProperty("Content-Type", mime == null ? "application/octet-stream" : mime); + connection.setRequestProperty("User-Agent", mXmppConnectionService.getIqGenerator().getIdentityName()); + connection.setDoOutput(true); connection.setConnectTimeout(Config.SOCKET_TIMEOUT * 1000); connection.setReadTimeout(Config.CONNECT_TIMEOUT * 1000); - connection.connect(); - os = connection.getOutputStream(); - transmitted = 0; - int count; - byte[] buffer = new byte[4096]; - while (((count = mFileInputStream.read(buffer)) != -1) && !canceled) { - transmitted += count; - os.write(buffer, 0, count); - mXmppConnectionService.updateConversationUi(); - } - os.flush(); - os.close(); - mFileInputStream.close(); - int code = connection.getResponseCode(); - if (code == 200 || code == 201) { - Log.d(Config.LOGTAG, "finished uploading file"); - if (key != null) { - mGetUrl = new URL(mGetUrl.toString() + "#" + CryptoHelper.bytesToHex(key)); - } - mXmppConnectionService.getFileBackend().updateFileParams(message, mGetUrl); - mXmppConnectionService.getFileBackend().updateMediaScanner(file); - message.setTransferable(null); - message.setCounterpart(message.getConversation().getJid().toBareJid()); - if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { - mXmppConnectionService.getPgpEngine().encrypt(message, new UiCallback() { - @Override - public void success(Message message) { - mXmppConnectionService.resendMessage(message,delayed); - } - - @Override - public void error(int errorCode, Message object) { - Log.d(Config.LOGTAG,"pgp encryption failed"); + connection.connect(); + os = connection.getOutputStream(); + transmitted = 0; + int count; + byte[] buffer = new byte[4096]; + while (((count = mFileInputStream.read(buffer)) != -1) && !canceled) { + transmitted += count; + os.write(buffer, 0, count); + mXmppConnectionService.updateConversationUi(); + } + os.flush(); + os.close(); + mFileInputStream.close(); + int code = connection.getResponseCode(); + if (code == 200 || code == 201) { + Log.d(Config.LOGTAG, "finished uploading file"); + if (key != null) { + mGetUrl = new URL(mGetUrl.toString() + "#" + CryptoHelper.bytesToHex(key)); + } + mXmppConnectionService.getFileBackend().updateFileParams(message, mGetUrl); + mXmppConnectionService.getFileBackend().updateMediaScanner(file); + message.setTransferable(null); + message.setCounterpart(message.getConversation().getJid().toBareJid()); + if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { + mXmppConnectionService.getPgpEngine().encrypt(message, new UiCallback() { + @Override + public void success(Message message) { + mXmppConnectionService.resendMessage(message, delayed); + } + + @Override + public void error(int errorCode, Message object) { + Log.d(Config.LOGTAG, "pgp encryption failed"); fail("pgp encryption failed"); - } + } - @Override - public void userInputRequried(PendingIntent pi, Message object) { + @Override + public void userInputRequried(PendingIntent pi, Message object) { fail("pgp encryption failed"); - } - }); - } else { - mXmppConnectionService.resendMessage(message, delayed); - } - } else { - Log.d(Config.LOGTAG,"http upload failed because response code was "+code); - fail("http upload failed because response code was "+code); - } - } catch (IOException e) { - e.printStackTrace(); - Log.d(Config.LOGTAG,"http upload failed "+e.getMessage()); - fail(e.getMessage()); - } finally { - FileBackend.close(mFileInputStream); - FileBackend.close(os); - if (connection != null) { - connection.disconnect(); - } - wakeLock.release(); - } - } - } + } + }); + } else { + mXmppConnectionService.resendMessage(message, delayed); + } + } else { + Log.d(Config.LOGTAG, "http upload failed because response code was " + code); + fail("http upload failed because response code was " + code); + } + } catch (IOException e) { + e.printStackTrace(); + Log.d(Config.LOGTAG, "http upload failed " + e.getMessage()); + fail(e.getMessage()); + } finally { + FileBackend.close(mFileInputStream); + FileBackend.close(os); + if (connection != null) { + connection.disconnect(); + } + wakeLock.release(); + } + } + } } diff --git a/src/main/java/de/pixart/messenger/parser/AbstractParser.java b/src/main/java/de/pixart/messenger/parser/AbstractParser.java index 3ad20783b..21da7fe67 100644 --- a/src/main/java/de/pixart/messenger/parser/AbstractParser.java +++ b/src/main/java/de/pixart/messenger/parser/AbstractParser.java @@ -15,88 +15,88 @@ import de.pixart.messenger.xmpp.jid.Jid; public abstract class AbstractParser { - protected XmppConnectionService mXmppConnectionService; + protected XmppConnectionService mXmppConnectionService; - protected AbstractParser(XmppConnectionService service) { - this.mXmppConnectionService = service; - } + protected AbstractParser(XmppConnectionService service) { + this.mXmppConnectionService = service; + } - public static Long parseTimestamp(Element element, Long d) { - Element delay = element.findChild("delay","urn:xmpp:delay"); - if (delay != null) { - String stamp = delay.getAttribute("stamp"); - if (stamp != null) { - try { - return AbstractParser.parseTimestamp(delay.getAttribute("stamp")); - } catch (ParseException e) { - return d; - } - } - } - return d; - } + public static Long parseTimestamp(Element element, Long d) { + Element delay = element.findChild("delay", "urn:xmpp:delay"); + if (delay != null) { + String stamp = delay.getAttribute("stamp"); + if (stamp != null) { + try { + return AbstractParser.parseTimestamp(delay.getAttribute("stamp")); + } catch (ParseException e) { + return d; + } + } + } + return d; + } - public static long parseTimestamp(Element element) { - return parseTimestamp(element, System.currentTimeMillis()); - } + public static long parseTimestamp(Element element) { + return parseTimestamp(element, System.currentTimeMillis()); + } - public static long parseTimestamp(String timestamp) throws ParseException { - timestamp = timestamp.replace("Z", "+0000"); - SimpleDateFormat dateFormat; - long ms; - if (timestamp.charAt(19) == '.' && timestamp.length() >= 25) { - String millis = timestamp.substring(19,timestamp.length() - 5); - try { - double fractions = Double.parseDouble("0" + millis); - ms = Math.round(1000 * fractions); - } catch (NumberFormatException e) { - ms = 0; - } - } else { - ms = 0; - } - timestamp = timestamp.substring(0,19)+timestamp.substring(timestamp.length() -5,timestamp.length()); - dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ",Locale.US); - return Math.min(dateFormat.parse(timestamp).getTime()+ms, System.currentTimeMillis()); - } + public static long parseTimestamp(String timestamp) throws ParseException { + timestamp = timestamp.replace("Z", "+0000"); + SimpleDateFormat dateFormat; + long ms; + if (timestamp.charAt(19) == '.' && timestamp.length() >= 25) { + String millis = timestamp.substring(19, timestamp.length() - 5); + try { + double fractions = Double.parseDouble("0" + millis); + ms = Math.round(1000 * fractions); + } catch (NumberFormatException e) { + ms = 0; + } + } else { + ms = 0; + } + timestamp = timestamp.substring(0, 19) + timestamp.substring(timestamp.length() - 5, timestamp.length()); + dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US); + return Math.min(dateFormat.parse(timestamp).getTime() + ms, System.currentTimeMillis()); + } - protected void updateLastseen(final Account account, final Jid from) { - final Contact contact = account.getRoster().getContact(from); - contact.setLastResource(from.isBareJid() ? "" : from.getResourcepart()); - } + protected void updateLastseen(final Account account, final Jid from) { + final Contact contact = account.getRoster().getContact(from); + contact.setLastResource(from.isBareJid() ? "" : from.getResourcepart()); + } - protected String avatarData(Element items) { - Element item = items.findChild("item"); - if (item == null) { - return null; - } - return item.findChildContent("data", "urn:xmpp:avatar:data"); - } + protected String avatarData(Element items) { + Element item = items.findChild("item"); + if (item == null) { + return null; + } + return item.findChildContent("data", "urn:xmpp:avatar:data"); + } - public static MucOptions.User parseItem(Conversation conference, Element item) { + public static MucOptions.User parseItem(Conversation conference, Element item) { return parseItem(conference, item, null); } public static MucOptions.User parseItem(Conversation conference, Element item, Jid fullJid) { - final String local = conference.getJid().getLocalpart(); - final String domain = conference.getJid().getDomainpart(); - String affiliation = item.getAttribute("affiliation"); - String role = item.getAttribute("role"); - String nick = item.getAttribute("nick"); + final String local = conference.getJid().getLocalpart(); + final String domain = conference.getJid().getDomainpart(); + String affiliation = item.getAttribute("affiliation"); + String role = item.getAttribute("role"); + String nick = item.getAttribute("nick"); if (nick != null && fullJid == null) { try { fullJid = Jid.fromParts(local, domain, nick); } catch (InvalidJidException e) { fullJid = null; } - } - Jid realJid = item.getAttributeAsJid("jid"); + } + Jid realJid = item.getAttributeAsJid("jid"); MucOptions.User user = new MucOptions.User(conference.getMucOptions(), fullJid); - user.setRealJid(realJid); - user.setAffiliation(affiliation); - user.setRole(role); - return user; - } + user.setRealJid(realJid); + user.setAffiliation(affiliation); + user.setRole(role); + return user; + } public static String extractErrorMessage(Element packet) { final Element error = packet.findChild("error"); @@ -105,7 +105,7 @@ public abstract class AbstractParser { if (text != null && !text.trim().isEmpty()) { return text; } else { - return error.getChildren().get(0).getName().replace("-"," "); + return error.getChildren().get(0).getName().replace("-", " "); } } else { return null; diff --git a/src/main/java/de/pixart/messenger/parser/IqParser.java b/src/main/java/de/pixart/messenger/parser/IqParser.java index 5617973d6..25d946c1d 100644 --- a/src/main/java/de/pixart/messenger/parser/IqParser.java +++ b/src/main/java/de/pixart/messenger/parser/IqParser.java @@ -36,351 +36,351 @@ import de.pixart.messenger.xmpp.stanzas.IqPacket; public class IqParser extends AbstractParser implements OnIqPacketReceived { - public IqParser(final XmppConnectionService service) { - super(service); - } + public IqParser(final XmppConnectionService service) { + super(service); + } - private void rosterItems(final Account account, final Element query) { - final String version = query.getAttribute("ver"); - if (version != null) { - account.getRoster().setVersion(version); - } - for (final Element item : query.getChildren()) { - if (item.getName().equals("item")) { - final Jid jid = item.getAttributeAsJid("jid"); - if (jid == null) { - continue; - } - final String name = item.getAttribute("name"); - final String subscription = item.getAttribute("subscription"); - final Contact contact = account.getRoster().getContact(jid); - boolean bothPre = contact.getOption(Contact.Options.TO) && contact.getOption(Contact.Options.FROM); - if (!contact.getOption(Contact.Options.DIRTY_PUSH)) { - contact.setServerName(name); - contact.parseGroupsFromElement(item); - } - if (subscription != null) { - if (subscription.equals("remove")) { - contact.resetOption(Contact.Options.IN_ROSTER); - contact.resetOption(Contact.Options.DIRTY_DELETE); - contact.resetOption(Contact.Options.PREEMPTIVE_GRANT); - } else { - contact.setOption(Contact.Options.IN_ROSTER); - contact.resetOption(Contact.Options.DIRTY_PUSH); - contact.parseSubscriptionFromElement(item); - } - } - boolean both = contact.getOption(Contact.Options.TO) && contact.getOption(Contact.Options.FROM); - if ((both != bothPre) && both) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": gained mutual presence subscription with "+contact.getJid()); - AxolotlService axolotlService = account.getAxolotlService(); - if (axolotlService != null) { - axolotlService.clearErrorsInFetchStatusMap(contact.getJid()); - } - } - mXmppConnectionService.getAvatarService().clear(contact); - } - } - mXmppConnectionService.updateConversationUi(); - mXmppConnectionService.updateRosterUi(); - } + private void rosterItems(final Account account, final Element query) { + final String version = query.getAttribute("ver"); + if (version != null) { + account.getRoster().setVersion(version); + } + for (final Element item : query.getChildren()) { + if (item.getName().equals("item")) { + final Jid jid = item.getAttributeAsJid("jid"); + if (jid == null) { + continue; + } + final String name = item.getAttribute("name"); + final String subscription = item.getAttribute("subscription"); + final Contact contact = account.getRoster().getContact(jid); + boolean bothPre = contact.getOption(Contact.Options.TO) && contact.getOption(Contact.Options.FROM); + if (!contact.getOption(Contact.Options.DIRTY_PUSH)) { + contact.setServerName(name); + contact.parseGroupsFromElement(item); + } + if (subscription != null) { + if (subscription.equals("remove")) { + contact.resetOption(Contact.Options.IN_ROSTER); + contact.resetOption(Contact.Options.DIRTY_DELETE); + contact.resetOption(Contact.Options.PREEMPTIVE_GRANT); + } else { + contact.setOption(Contact.Options.IN_ROSTER); + contact.resetOption(Contact.Options.DIRTY_PUSH); + contact.parseSubscriptionFromElement(item); + } + } + boolean both = contact.getOption(Contact.Options.TO) && contact.getOption(Contact.Options.FROM); + if ((both != bothPre) && both) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": gained mutual presence subscription with " + contact.getJid()); + AxolotlService axolotlService = account.getAxolotlService(); + if (axolotlService != null) { + axolotlService.clearErrorsInFetchStatusMap(contact.getJid()); + } + } + mXmppConnectionService.getAvatarService().clear(contact); + } + } + mXmppConnectionService.updateConversationUi(); + mXmppConnectionService.updateRosterUi(); + } - public String avatarData(final IqPacket packet) { - final Element pubsub = packet.findChild("pubsub", - "http://jabber.org/protocol/pubsub"); - if (pubsub == null) { - return null; - } - final Element items = pubsub.findChild("items"); - if (items == null) { - return null; - } - return super.avatarData(items); - } + public String avatarData(final IqPacket packet) { + final Element pubsub = packet.findChild("pubsub", + "http://jabber.org/protocol/pubsub"); + if (pubsub == null) { + return null; + } + final Element items = pubsub.findChild("items"); + if (items == null) { + return null; + } + return super.avatarData(items); + } - public Element getItem(final IqPacket packet) { - final Element pubsub = packet.findChild("pubsub", - "http://jabber.org/protocol/pubsub"); - if (pubsub == null) { - return null; - } - final Element items = pubsub.findChild("items"); - if (items == null) { - return null; - } - return items.findChild("item"); - } + public Element getItem(final IqPacket packet) { + final Element pubsub = packet.findChild("pubsub", + "http://jabber.org/protocol/pubsub"); + if (pubsub == null) { + return null; + } + final Element items = pubsub.findChild("items"); + if (items == null) { + return null; + } + return items.findChild("item"); + } - @NonNull - public Set deviceIds(final Element item) { - Set deviceIds = new HashSet<>(); - if (item != null) { - final Element list = item.findChild("list"); - if (list != null) { - for (Element device : list.getChildren()) { - if (!device.getName().equals("device")) { - continue; - } - try { - Integer id = Integer.valueOf(device.getAttribute("id")); - deviceIds.add(id); - } catch (NumberFormatException e) { - Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Encountered invalid node in PEP ("+e.getMessage()+"):" + device.toString()+ ", skipping..."); - continue; - } - } - } - } - return deviceIds; - } + @NonNull + public Set deviceIds(final Element item) { + Set deviceIds = new HashSet<>(); + if (item != null) { + final Element list = item.findChild("list"); + if (list != null) { + for (Element device : list.getChildren()) { + if (!device.getName().equals("device")) { + continue; + } + try { + Integer id = Integer.valueOf(device.getAttribute("id")); + deviceIds.add(id); + } catch (NumberFormatException e) { + Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Encountered invalid node in PEP (" + e.getMessage() + "):" + device.toString() + ", skipping..."); + continue; + } + } + } + } + return deviceIds; + } - public Integer signedPreKeyId(final Element bundle) { - final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic"); - if(signedPreKeyPublic == null) { - return null; - } + public Integer signedPreKeyId(final Element bundle) { + final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic"); + if (signedPreKeyPublic == null) { + return null; + } try { return Integer.valueOf(signedPreKeyPublic.getAttribute("signedPreKeyId")); } catch (NumberFormatException e) { return null; } - } + } - public ECPublicKey signedPreKeyPublic(final Element bundle) { - ECPublicKey publicKey = null; - final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic"); - if(signedPreKeyPublic == null) { - return null; - } - try { - publicKey = Curve.decodePoint(Base64.decode(signedPreKeyPublic.getContent(),Base64.DEFAULT), 0); - } catch (Throwable e) { - Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Invalid signedPreKeyPublic in PEP: " + e.getMessage()); - } - return publicKey; - } + public ECPublicKey signedPreKeyPublic(final Element bundle) { + ECPublicKey publicKey = null; + final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic"); + if (signedPreKeyPublic == null) { + return null; + } + try { + publicKey = Curve.decodePoint(Base64.decode(signedPreKeyPublic.getContent(), Base64.DEFAULT), 0); + } catch (Throwable e) { + Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Invalid signedPreKeyPublic in PEP: " + e.getMessage()); + } + return publicKey; + } - public byte[] signedPreKeySignature(final Element bundle) { - final Element signedPreKeySignature = bundle.findChild("signedPreKeySignature"); - if(signedPreKeySignature == null) { - return null; - } - try { - return Base64.decode(signedPreKeySignature.getContent(), Base64.DEFAULT); - } catch (Throwable e) { - Log.e(Config.LOGTAG,AxolotlService.LOGPREFIX+" : Invalid base64 in signedPreKeySignature"); - return null; - } - } + public byte[] signedPreKeySignature(final Element bundle) { + final Element signedPreKeySignature = bundle.findChild("signedPreKeySignature"); + if (signedPreKeySignature == null) { + return null; + } + try { + return Base64.decode(signedPreKeySignature.getContent(), Base64.DEFAULT); + } catch (Throwable e) { + Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : Invalid base64 in signedPreKeySignature"); + return null; + } + } - public IdentityKey identityKey(final Element bundle) { - IdentityKey identityKey = null; - final Element identityKeyElement = bundle.findChild("identityKey"); - if(identityKeyElement == null) { - return null; - } - try { - identityKey = new IdentityKey(Base64.decode(identityKeyElement.getContent(), Base64.DEFAULT), 0); - } catch (Throwable e) { - Log.e(Config.LOGTAG,AxolotlService.LOGPREFIX+" : "+"Invalid identityKey in PEP: "+e.getMessage()); - } - return identityKey; - } + public IdentityKey identityKey(final Element bundle) { + IdentityKey identityKey = null; + final Element identityKeyElement = bundle.findChild("identityKey"); + if (identityKeyElement == null) { + return null; + } + try { + identityKey = new IdentityKey(Base64.decode(identityKeyElement.getContent(), Base64.DEFAULT), 0); + } catch (Throwable e) { + Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Invalid identityKey in PEP: " + e.getMessage()); + } + return identityKey; + } - public Map preKeyPublics(final IqPacket packet) { - Map preKeyRecords = new HashMap<>(); - Element item = getItem(packet); - if (item == null) { - Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Couldn't find in bundle IQ packet: " + packet); - return null; - } - final Element bundleElement = item.findChild("bundle"); - if(bundleElement == null) { - return null; - } - final Element prekeysElement = bundleElement.findChild("prekeys"); - if(prekeysElement == null) { - Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Couldn't find in bundle IQ packet: " + packet); - return null; - } - for(Element preKeyPublicElement : prekeysElement.getChildren()) { - if(!preKeyPublicElement.getName().equals("preKeyPublic")){ - Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Encountered unexpected tag in prekeys list: " + preKeyPublicElement); - continue; - } - Integer preKeyId = null; - try { - preKeyId = Integer.valueOf(preKeyPublicElement.getAttribute("preKeyId")); - final ECPublicKey preKeyPublic = Curve.decodePoint(Base64.decode(preKeyPublicElement.getContent(), Base64.DEFAULT), 0); - preKeyRecords.put(preKeyId, preKeyPublic); - } catch (NumberFormatException e) { - Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"could not parse preKeyId from preKey "+preKeyPublicElement.toString()); - } catch (Throwable e) { - Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Invalid preKeyPublic (ID="+preKeyId+") in PEP: "+ e.getMessage()+", skipping..."); - } - } - return preKeyRecords; - } + public Map preKeyPublics(final IqPacket packet) { + Map preKeyRecords = new HashMap<>(); + Element item = getItem(packet); + if (item == null) { + Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Couldn't find in bundle IQ packet: " + packet); + return null; + } + final Element bundleElement = item.findChild("bundle"); + if (bundleElement == null) { + return null; + } + final Element prekeysElement = bundleElement.findChild("prekeys"); + if (prekeysElement == null) { + Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Couldn't find in bundle IQ packet: " + packet); + return null; + } + for (Element preKeyPublicElement : prekeysElement.getChildren()) { + if (!preKeyPublicElement.getName().equals("preKeyPublic")) { + Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Encountered unexpected tag in prekeys list: " + preKeyPublicElement); + continue; + } + Integer preKeyId = null; + try { + preKeyId = Integer.valueOf(preKeyPublicElement.getAttribute("preKeyId")); + final ECPublicKey preKeyPublic = Curve.decodePoint(Base64.decode(preKeyPublicElement.getContent(), Base64.DEFAULT), 0); + preKeyRecords.put(preKeyId, preKeyPublic); + } catch (NumberFormatException e) { + Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "could not parse preKeyId from preKey " + preKeyPublicElement.toString()); + } catch (Throwable e) { + Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Invalid preKeyPublic (ID=" + preKeyId + ") in PEP: " + e.getMessage() + ", skipping..."); + } + } + return preKeyRecords; + } - public Pair verification(final IqPacket packet) { - Element item = getItem(packet); - Element verification = item != null ? item.findChild("verification",AxolotlService.PEP_PREFIX) : null; - Element chain = verification != null ? verification.findChild("chain") : null; - Element signature = verification != null ? verification.findChild("signature") : null; - if (chain != null && signature != null) { - List certElements = chain.getChildren(); - X509Certificate[] certificates = new X509Certificate[certElements.size()]; - try { - CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); - int i = 0; - for(Element cert : certElements) { - certificates[i] = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(Base64.decode(cert.getContent(),Base64.DEFAULT))); - ++i; - } - return new Pair<>(certificates,Base64.decode(signature.getContent(),Base64.DEFAULT)); - } catch (CertificateException e) { - return null; - } - } else { - return null; - } - } + public Pair verification(final IqPacket packet) { + Element item = getItem(packet); + Element verification = item != null ? item.findChild("verification", AxolotlService.PEP_PREFIX) : null; + Element chain = verification != null ? verification.findChild("chain") : null; + Element signature = verification != null ? verification.findChild("signature") : null; + if (chain != null && signature != null) { + List certElements = chain.getChildren(); + X509Certificate[] certificates = new X509Certificate[certElements.size()]; + try { + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + int i = 0; + for (Element cert : certElements) { + certificates[i] = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(Base64.decode(cert.getContent(), Base64.DEFAULT))); + ++i; + } + return new Pair<>(certificates, Base64.decode(signature.getContent(), Base64.DEFAULT)); + } catch (CertificateException e) { + return null; + } + } else { + return null; + } + } - public PreKeyBundle bundle(final IqPacket bundle) { - Element bundleItem = getItem(bundle); - if(bundleItem == null) { - return null; - } - final Element bundleElement = bundleItem.findChild("bundle"); - if(bundleElement == null) { - return null; - } - ECPublicKey signedPreKeyPublic = signedPreKeyPublic(bundleElement); - Integer signedPreKeyId = signedPreKeyId(bundleElement); - byte[] signedPreKeySignature = signedPreKeySignature(bundleElement); - IdentityKey identityKey = identityKey(bundleElement); - if(signedPreKeyId == null || signedPreKeyPublic == null || identityKey == null) { - return null; - } + public PreKeyBundle bundle(final IqPacket bundle) { + Element bundleItem = getItem(bundle); + if (bundleItem == null) { + return null; + } + final Element bundleElement = bundleItem.findChild("bundle"); + if (bundleElement == null) { + return null; + } + ECPublicKey signedPreKeyPublic = signedPreKeyPublic(bundleElement); + Integer signedPreKeyId = signedPreKeyId(bundleElement); + byte[] signedPreKeySignature = signedPreKeySignature(bundleElement); + IdentityKey identityKey = identityKey(bundleElement); + if (signedPreKeyId == null || signedPreKeyPublic == null || identityKey == null) { + return null; + } - return new PreKeyBundle(0, 0, 0, null, - signedPreKeyId, signedPreKeyPublic, signedPreKeySignature, identityKey); - } + return new PreKeyBundle(0, 0, 0, null, + signedPreKeyId, signedPreKeyPublic, signedPreKeySignature, identityKey); + } - public List preKeys(final IqPacket preKeys) { - List bundles = new ArrayList<>(); - Map preKeyPublics = preKeyPublics(preKeys); - if ( preKeyPublics != null) { - for (Integer preKeyId : preKeyPublics.keySet()) { - ECPublicKey preKeyPublic = preKeyPublics.get(preKeyId); - bundles.add(new PreKeyBundle(0, 0, preKeyId, preKeyPublic, - 0, null, null, null)); - } - } + public List preKeys(final IqPacket preKeys) { + List bundles = new ArrayList<>(); + Map preKeyPublics = preKeyPublics(preKeys); + if (preKeyPublics != null) { + for (Integer preKeyId : preKeyPublics.keySet()) { + ECPublicKey preKeyPublic = preKeyPublics.get(preKeyId); + bundles.add(new PreKeyBundle(0, 0, preKeyId, preKeyPublic, + 0, null, null, null)); + } + } - return bundles; - } + return bundles; + } - @Override - public void onIqPacketReceived(final Account account, final IqPacket packet) { - final boolean isGet = packet.getType() == IqPacket.TYPE.GET; - if (packet.getType() == IqPacket.TYPE.ERROR || packet.getType() == IqPacket.TYPE.TIMEOUT) { - return; - } else if (packet.hasChild("query", Xmlns.ROSTER) && packet.fromServer(account)) { - final Element query = packet.findChild("query"); - // If this is in response to a query for the whole roster: - if (packet.getType() == IqPacket.TYPE.RESULT) { - account.getRoster().markAllAsNotInRoster(); - } - this.rosterItems(account, query); - } else if ((packet.hasChild("block", Xmlns.BLOCKING) || packet.hasChild("blocklist", Xmlns.BLOCKING)) && - packet.fromServer(account)) { - // Block list or block push. - Log.d(Config.LOGTAG, "Received blocklist update from server"); - final Element blocklist = packet.findChild("blocklist", Xmlns.BLOCKING); - final Element block = packet.findChild("block", Xmlns.BLOCKING); - final Collection items = blocklist != null ? blocklist.getChildren() : - (block != null ? block.getChildren() : null); - // If this is a response to a blocklist query, clear the block list and replace with the new one. - // Otherwise, just update the existing blocklist. - if (packet.getType() == IqPacket.TYPE.RESULT) { - account.clearBlocklist(); - account.getXmppConnection().getFeatures().setBlockListRequested(true); - } - if (items != null) { - final Collection jids = new ArrayList<>(items.size()); - // Create a collection of Jids from the packet - for (final Element item : items) { - if (item.getName().equals("item")) { - final Jid jid = item.getAttributeAsJid("jid"); - if (jid != null) { - jids.add(jid); - } - } - } - account.getBlocklist().addAll(jids); - } - // Update the UI - mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED); - if (packet.getType() == IqPacket.TYPE.SET) { - final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT); - mXmppConnectionService.sendIqPacket(account, response, null); - } - } else if (packet.hasChild("unblock", Xmlns.BLOCKING) && - packet.fromServer(account) && packet.getType() == IqPacket.TYPE.SET) { - Log.d(Config.LOGTAG, "Received unblock update from server"); - final Collection items = packet.findChild("unblock", Xmlns.BLOCKING).getChildren(); - if (items.size() == 0) { - // No children to unblock == unblock all - account.getBlocklist().clear(); - } else { - final Collection jids = new ArrayList<>(items.size()); - for (final Element item : items) { - if (item.getName().equals("item")) { - final Jid jid = item.getAttributeAsJid("jid"); - if (jid != null) { - jids.add(jid); - } - } - } - account.getBlocklist().removeAll(jids); - } - mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED); - final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT); - mXmppConnectionService.sendIqPacket(account, response, null); - } else if (packet.hasChild("open", "http://jabber.org/protocol/ibb") - || packet.hasChild("data", "http://jabber.org/protocol/ibb")) { - mXmppConnectionService.getJingleConnectionManager() - .deliverIbbPacket(account, packet); - } else if (packet.hasChild("query", "http://jabber.org/protocol/disco#info")) { - final IqPacket response = mXmppConnectionService.getIqGenerator().discoResponse(packet); - mXmppConnectionService.sendIqPacket(account, response, null); - } else if (packet.hasChild("query","jabber:iq:version") && isGet) { - final IqPacket response = mXmppConnectionService.getIqGenerator().versionResponse(packet); - mXmppConnectionService.sendIqPacket(account,response,null); - } else if (packet.hasChild("ping", "urn:xmpp:ping") && isGet) { - final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT); - mXmppConnectionService.sendIqPacket(account, response, null); - } else if (packet.hasChild("time","urn:xmpp:time") && isGet) { - final IqPacket response; - if (mXmppConnectionService.useTorToConnect()) { - response = packet.generateResponse(IqPacket.TYPE.ERROR); - final Element error = response.addChild("error"); - error.setAttribute("type","cancel"); - error.addChild("not-allowed","urn:ietf:params:xml:ns:xmpp-stanzas"); - } else { - response = mXmppConnectionService.getIqGenerator().entityTimeResponse(packet); - } - mXmppConnectionService.sendIqPacket(account,response, null); - } else { - if (packet.getType() == IqPacket.TYPE.GET || packet.getType() == IqPacket.TYPE.SET) { - final IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR); - final Element error = response.addChild("error"); - error.setAttribute("type", "cancel"); - error.addChild("feature-not-implemented","urn:ietf:params:xml:ns:xmpp-stanzas"); - account.getXmppConnection().sendIqPacket(response, null); - } - } - } + @Override + public void onIqPacketReceived(final Account account, final IqPacket packet) { + final boolean isGet = packet.getType() == IqPacket.TYPE.GET; + if (packet.getType() == IqPacket.TYPE.ERROR || packet.getType() == IqPacket.TYPE.TIMEOUT) { + return; + } else if (packet.hasChild("query", Xmlns.ROSTER) && packet.fromServer(account)) { + final Element query = packet.findChild("query"); + // If this is in response to a query for the whole roster: + if (packet.getType() == IqPacket.TYPE.RESULT) { + account.getRoster().markAllAsNotInRoster(); + } + this.rosterItems(account, query); + } else if ((packet.hasChild("block", Xmlns.BLOCKING) || packet.hasChild("blocklist", Xmlns.BLOCKING)) && + packet.fromServer(account)) { + // Block list or block push. + Log.d(Config.LOGTAG, "Received blocklist update from server"); + final Element blocklist = packet.findChild("blocklist", Xmlns.BLOCKING); + final Element block = packet.findChild("block", Xmlns.BLOCKING); + final Collection items = blocklist != null ? blocklist.getChildren() : + (block != null ? block.getChildren() : null); + // If this is a response to a blocklist query, clear the block list and replace with the new one. + // Otherwise, just update the existing blocklist. + if (packet.getType() == IqPacket.TYPE.RESULT) { + account.clearBlocklist(); + account.getXmppConnection().getFeatures().setBlockListRequested(true); + } + if (items != null) { + final Collection jids = new ArrayList<>(items.size()); + // Create a collection of Jids from the packet + for (final Element item : items) { + if (item.getName().equals("item")) { + final Jid jid = item.getAttributeAsJid("jid"); + if (jid != null) { + jids.add(jid); + } + } + } + account.getBlocklist().addAll(jids); + } + // Update the UI + mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED); + if (packet.getType() == IqPacket.TYPE.SET) { + final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT); + mXmppConnectionService.sendIqPacket(account, response, null); + } + } else if (packet.hasChild("unblock", Xmlns.BLOCKING) && + packet.fromServer(account) && packet.getType() == IqPacket.TYPE.SET) { + Log.d(Config.LOGTAG, "Received unblock update from server"); + final Collection items = packet.findChild("unblock", Xmlns.BLOCKING).getChildren(); + if (items.size() == 0) { + // No children to unblock == unblock all + account.getBlocklist().clear(); + } else { + final Collection jids = new ArrayList<>(items.size()); + for (final Element item : items) { + if (item.getName().equals("item")) { + final Jid jid = item.getAttributeAsJid("jid"); + if (jid != null) { + jids.add(jid); + } + } + } + account.getBlocklist().removeAll(jids); + } + mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED); + final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT); + mXmppConnectionService.sendIqPacket(account, response, null); + } else if (packet.hasChild("open", "http://jabber.org/protocol/ibb") + || packet.hasChild("data", "http://jabber.org/protocol/ibb")) { + mXmppConnectionService.getJingleConnectionManager() + .deliverIbbPacket(account, packet); + } else if (packet.hasChild("query", "http://jabber.org/protocol/disco#info")) { + final IqPacket response = mXmppConnectionService.getIqGenerator().discoResponse(packet); + mXmppConnectionService.sendIqPacket(account, response, null); + } else if (packet.hasChild("query", "jabber:iq:version") && isGet) { + final IqPacket response = mXmppConnectionService.getIqGenerator().versionResponse(packet); + mXmppConnectionService.sendIqPacket(account, response, null); + } else if (packet.hasChild("ping", "urn:xmpp:ping") && isGet) { + final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT); + mXmppConnectionService.sendIqPacket(account, response, null); + } else if (packet.hasChild("time", "urn:xmpp:time") && isGet) { + final IqPacket response; + if (mXmppConnectionService.useTorToConnect()) { + response = packet.generateResponse(IqPacket.TYPE.ERROR); + final Element error = response.addChild("error"); + error.setAttribute("type", "cancel"); + error.addChild("not-allowed", "urn:ietf:params:xml:ns:xmpp-stanzas"); + } else { + response = mXmppConnectionService.getIqGenerator().entityTimeResponse(packet); + } + mXmppConnectionService.sendIqPacket(account, response, null); + } else { + if (packet.getType() == IqPacket.TYPE.GET || packet.getType() == IqPacket.TYPE.SET) { + final IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR); + final Element error = response.addChild("error"); + error.setAttribute("type", "cancel"); + error.addChild("feature-not-implemented", "urn:ietf:params:xml:ns:xmpp-stanzas"); + account.getXmppConnection().sendIqPacket(response, null); + } + } + } } diff --git a/src/main/java/de/pixart/messenger/parser/MessageParser.java b/src/main/java/de/pixart/messenger/parser/MessageParser.java index d506c3fa2..531e29629 100644 --- a/src/main/java/de/pixart/messenger/parser/MessageParser.java +++ b/src/main/java/de/pixart/messenger/parser/MessageParser.java @@ -41,396 +41,397 @@ import de.pixart.messenger.xmpp.stanzas.MessagePacket; public class MessageParser extends AbstractParser implements OnMessagePacketReceived { - private static final List CLIENTS_SENDING_HTML_IN_OTR = Arrays.asList("Pidgin","Adium","Trillian"); - - public MessageParser(XmppConnectionService service) { - super(service); - } - - private boolean extractChatState(Conversation conversation, final MessagePacket packet) { - ChatState state = ChatState.parse(packet); - if (state != null && conversation != null) { - final Account account = conversation.getAccount(); - Jid from = packet.getFrom(); - if (from.toBareJid().equals(account.getJid().toBareJid())) { - conversation.setOutgoingChatState(state); - if (state == ChatState.ACTIVE || state == ChatState.COMPOSING) { - mXmppConnectionService.markRead(conversation); - activateGracePeriod(account); - } - return false; - } else { - return conversation.setIncomingChatState(state); - } - } - return false; - } - - private Message parseOtrChat(String body, Jid from, String id, Conversation conversation) { - String presence; - if (from.isBareJid()) { - presence = ""; - } else { - presence = from.getResourcepart(); - } - if (body.matches("^\\?OTRv\\d{1,2}\\?.*")) { - conversation.endOtrIfNeeded(); - } - if (!conversation.hasValidOtrSession()) { - conversation.startOtrSession(presence,false); - } else { - String foreignPresence = conversation.getOtrSession().getSessionID().getUserID(); - if (!foreignPresence.equals(presence)) { - conversation.endOtrIfNeeded(); - conversation.startOtrSession(presence, false); - } - } - try { - conversation.setLastReceivedOtrMessageId(id); - Session otrSession = conversation.getOtrSession(); - body = otrSession.transformReceiving(body); - SessionStatus status = otrSession.getSessionStatus(); - if (body == null && status == SessionStatus.ENCRYPTED) { - mXmppConnectionService.onOtrSessionEstablished(conversation); - return null; - } else if (body == null && status == SessionStatus.FINISHED) { - conversation.resetOtrSession(); - mXmppConnectionService.updateConversationUi(); - return null; - } else if (body == null || (body.isEmpty())) { - return null; - } - if (body.startsWith(CryptoHelper.FILETRANSFER)) { - String key = body.substring(CryptoHelper.FILETRANSFER.length()); - conversation.setSymmetricKey(CryptoHelper.hexToBytes(key)); - return null; - } - if (clientMightSendHtml(conversation.getAccount(), from)) { - Log.d(Config.LOGTAG,conversation.getAccount().getJid().toBareJid()+": received OTR message from bad behaving client. escaping HTML…"); - body = Html.fromHtml(body).toString(); - } - - final OtrService otrService = conversation.getAccount().getOtrService(); - Message finishedMessage = new Message(conversation, body, Message.ENCRYPTION_OTR, Message.STATUS_RECEIVED); - finishedMessage.setFingerprint(otrService.getFingerprint(otrSession.getRemotePublicKey())); - conversation.setLastReceivedOtrMessageId(null); - - return finishedMessage; - } catch (Exception e) { - conversation.resetOtrSession(); - return null; - } - } - - private static boolean clientMightSendHtml(Account account, Jid from) { - String resource = from.getResourcepart(); - if (resource == null) { - return false; - } - Presence presence = account.getRoster().getContact(from).getPresences().getPresences().get(resource); - ServiceDiscoveryResult disco = presence == null ? null : presence.getServiceDiscoveryResult(); - if (disco == null) { - return false; - } - return hasIdentityKnowForSendingHtml(disco.getIdentities()); - } - - private static boolean hasIdentityKnowForSendingHtml(List identities) { - for(ServiceDiscoveryResult.Identity identity : identities) { - if (identity.getName() != null) { - if (CLIENTS_SENDING_HTML_IN_OTR.contains(identity.getName())) { - return true; - } - } - } - return false; - } - - private Message parseAxolotlChat(Element axolotlMessage, Jid from, Conversation conversation, int status) { - AxolotlService service = conversation.getAccount().getAxolotlService(); - XmppAxolotlMessage xmppAxolotlMessage; - try { - xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlMessage, from.toBareJid()); - } catch (Exception e) { - Log.d(Config.LOGTAG,conversation.getAccount().getJid().toBareJid()+": invalid omemo message received "+e.getMessage()); - return null; - } - XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = service.processReceivingPayloadMessage(xmppAxolotlMessage); - if(plaintextMessage != null) { - Message finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status); - finishedMessage.setFingerprint(plaintextMessage.getFingerprint()); - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(finishedMessage.getConversation().getAccount())+" Received Message with session fingerprint: "+plaintextMessage.getFingerprint()); - return finishedMessage; - } else { - return null; - } - } - - private class Invite { - Jid jid; - String password; - Invite(Jid jid, String password) { - this.jid = jid; - this.password = password; - } - - public boolean execute(Account account) { - if (jid != null) { - Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, jid, true); - if (!conversation.getMucOptions().online()) { - conversation.getMucOptions().setPassword(password); - mXmppConnectionService.databaseBackend.updateConversation(conversation); - mXmppConnectionService.joinMuc(conversation); - mXmppConnectionService.updateConversationUi(); - } - return true; - } - return false; - } - } - - private Invite extractInvite(Element message) { - Element x = message.findChild("x", "http://jabber.org/protocol/muc#user"); - if (x != null) { - Element invite = x.findChild("invite"); - if (invite != null) { - Element pw = x.findChild("password"); - return new Invite(message.getAttributeAsJid("from"), pw != null ? pw.getContent(): null); - } - } else { - x = message.findChild("x","jabber:x:conference"); - if (x != null) { - return new Invite(x.getAttributeAsJid("jid"),x.getAttribute("password")); - } - } - return null; - } - - private static String extractStanzaId(Element packet, Jid by) { - for(Element child : packet.getChildren()) { - if (child.getName().equals("stanza-id") - && Xmlns.STANZA_IDS.equals(child.getNamespace()) - && by.equals(child.getAttributeAsJid("by"))) { - return child.getAttribute("id"); - } - } - return null; - } - - private void parseEvent(final Element event, final Jid from, final Account account) { - Element items = event.findChild("items"); - String node = items == null ? null : items.getAttribute("node"); - if ("urn:xmpp:avatar:metadata".equals(node)) { - Avatar avatar = Avatar.parseMetadata(items); - if (avatar != null) { - avatar.owner = from.toBareJid(); - if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) { - if (account.getJid().toBareJid().equals(from)) { - if (account.setAvatar(avatar.getFilename())) { - mXmppConnectionService.databaseBackend.updateAccount(account); - } - mXmppConnectionService.getAvatarService().clear(account); - mXmppConnectionService.updateConversationUi(); - mXmppConnectionService.updateAccountUi(); - } else { - Contact contact = account.getRoster().getContact(from); - contact.setAvatar(avatar); - mXmppConnectionService.getAvatarService().clear(contact); - mXmppConnectionService.updateConversationUi(); - mXmppConnectionService.updateRosterUi(); - } - } else if (mXmppConnectionService.isDataSaverDisabled()) { - mXmppConnectionService.fetchAvatar(account, avatar); - } - } - } else if ("http://jabber.org/protocol/nick".equals(node)) { - Element i = items.findChild("item"); - Element nick = i == null ? null : i.findChild("nick", "http://jabber.org/protocol/nick"); - if (nick != null && nick.getContent() != null) { - Contact contact = account.getRoster().getContact(from); - contact.setPresenceName(nick.getContent()); - mXmppConnectionService.getAvatarService().clear(account); - mXmppConnectionService.updateConversationUi(); - mXmppConnectionService.updateAccountUi(); - } - } else if (AxolotlService.PEP_DEVICE_LIST.equals(node)) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Received PEP device list update from "+ from + ", processing..."); - Element item = items.findChild("item"); - Set deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); - AxolotlService axolotlService = account.getAxolotlService(); - axolotlService.registerDevices(from, deviceIds); - mXmppConnectionService.updateAccountUi(); - } - } - - private boolean handleErrorMessage(Account account, MessagePacket packet) { - if (packet.getType() == MessagePacket.TYPE_ERROR) { - Jid from = packet.getFrom(); - if (from != null) { - Message message = mXmppConnectionService.markMessage(account, - from.toBareJid(), - packet.getId(), + private static final List CLIENTS_SENDING_HTML_IN_OTR = Arrays.asList("Pidgin", "Adium", "Trillian"); + + public MessageParser(XmppConnectionService service) { + super(service); + } + + private boolean extractChatState(Conversation conversation, final MessagePacket packet) { + ChatState state = ChatState.parse(packet); + if (state != null && conversation != null) { + final Account account = conversation.getAccount(); + Jid from = packet.getFrom(); + if (from.toBareJid().equals(account.getJid().toBareJid())) { + conversation.setOutgoingChatState(state); + if (state == ChatState.ACTIVE || state == ChatState.COMPOSING) { + mXmppConnectionService.markRead(conversation); + activateGracePeriod(account); + } + return false; + } else { + return conversation.setIncomingChatState(state); + } + } + return false; + } + + private Message parseOtrChat(String body, Jid from, String id, Conversation conversation) { + String presence; + if (from.isBareJid()) { + presence = ""; + } else { + presence = from.getResourcepart(); + } + if (body.matches("^\\?OTRv\\d{1,2}\\?.*")) { + conversation.endOtrIfNeeded(); + } + if (!conversation.hasValidOtrSession()) { + conversation.startOtrSession(presence, false); + } else { + String foreignPresence = conversation.getOtrSession().getSessionID().getUserID(); + if (!foreignPresence.equals(presence)) { + conversation.endOtrIfNeeded(); + conversation.startOtrSession(presence, false); + } + } + try { + conversation.setLastReceivedOtrMessageId(id); + Session otrSession = conversation.getOtrSession(); + body = otrSession.transformReceiving(body); + SessionStatus status = otrSession.getSessionStatus(); + if (body == null && status == SessionStatus.ENCRYPTED) { + mXmppConnectionService.onOtrSessionEstablished(conversation); + return null; + } else if (body == null && status == SessionStatus.FINISHED) { + conversation.resetOtrSession(); + mXmppConnectionService.updateConversationUi(); + return null; + } else if (body == null || (body.isEmpty())) { + return null; + } + if (body.startsWith(CryptoHelper.FILETRANSFER)) { + String key = body.substring(CryptoHelper.FILETRANSFER.length()); + conversation.setSymmetricKey(CryptoHelper.hexToBytes(key)); + return null; + } + if (clientMightSendHtml(conversation.getAccount(), from)) { + Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + ": received OTR message from bad behaving client. escaping HTML…"); + body = Html.fromHtml(body).toString(); + } + + final OtrService otrService = conversation.getAccount().getOtrService(); + Message finishedMessage = new Message(conversation, body, Message.ENCRYPTION_OTR, Message.STATUS_RECEIVED); + finishedMessage.setFingerprint(otrService.getFingerprint(otrSession.getRemotePublicKey())); + conversation.setLastReceivedOtrMessageId(null); + + return finishedMessage; + } catch (Exception e) { + conversation.resetOtrSession(); + return null; + } + } + + private static boolean clientMightSendHtml(Account account, Jid from) { + String resource = from.getResourcepart(); + if (resource == null) { + return false; + } + Presence presence = account.getRoster().getContact(from).getPresences().getPresences().get(resource); + ServiceDiscoveryResult disco = presence == null ? null : presence.getServiceDiscoveryResult(); + if (disco == null) { + return false; + } + return hasIdentityKnowForSendingHtml(disco.getIdentities()); + } + + private static boolean hasIdentityKnowForSendingHtml(List identities) { + for (ServiceDiscoveryResult.Identity identity : identities) { + if (identity.getName() != null) { + if (CLIENTS_SENDING_HTML_IN_OTR.contains(identity.getName())) { + return true; + } + } + } + return false; + } + + private Message parseAxolotlChat(Element axolotlMessage, Jid from, Conversation conversation, int status) { + AxolotlService service = conversation.getAccount().getAxolotlService(); + XmppAxolotlMessage xmppAxolotlMessage; + try { + xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlMessage, from.toBareJid()); + } catch (Exception e) { + Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + ": invalid omemo message received " + e.getMessage()); + return null; + } + XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = service.processReceivingPayloadMessage(xmppAxolotlMessage); + if (plaintextMessage != null) { + Message finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status); + finishedMessage.setFingerprint(plaintextMessage.getFingerprint()); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(finishedMessage.getConversation().getAccount()) + " Received Message with session fingerprint: " + plaintextMessage.getFingerprint()); + return finishedMessage; + } else { + return null; + } + } + + private class Invite { + Jid jid; + String password; + + Invite(Jid jid, String password) { + this.jid = jid; + this.password = password; + } + + public boolean execute(Account account) { + if (jid != null) { + Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, jid, true); + if (!conversation.getMucOptions().online()) { + conversation.getMucOptions().setPassword(password); + mXmppConnectionService.databaseBackend.updateConversation(conversation); + mXmppConnectionService.joinMuc(conversation); + mXmppConnectionService.updateConversationUi(); + } + return true; + } + return false; + } + } + + private Invite extractInvite(Element message) { + Element x = message.findChild("x", "http://jabber.org/protocol/muc#user"); + if (x != null) { + Element invite = x.findChild("invite"); + if (invite != null) { + Element pw = x.findChild("password"); + return new Invite(message.getAttributeAsJid("from"), pw != null ? pw.getContent() : null); + } + } else { + x = message.findChild("x", "jabber:x:conference"); + if (x != null) { + return new Invite(x.getAttributeAsJid("jid"), x.getAttribute("password")); + } + } + return null; + } + + private static String extractStanzaId(Element packet, Jid by) { + for (Element child : packet.getChildren()) { + if (child.getName().equals("stanza-id") + && Xmlns.STANZA_IDS.equals(child.getNamespace()) + && by.equals(child.getAttributeAsJid("by"))) { + return child.getAttribute("id"); + } + } + return null; + } + + private void parseEvent(final Element event, final Jid from, final Account account) { + Element items = event.findChild("items"); + String node = items == null ? null : items.getAttribute("node"); + if ("urn:xmpp:avatar:metadata".equals(node)) { + Avatar avatar = Avatar.parseMetadata(items); + if (avatar != null) { + avatar.owner = from.toBareJid(); + if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) { + if (account.getJid().toBareJid().equals(from)) { + if (account.setAvatar(avatar.getFilename())) { + mXmppConnectionService.databaseBackend.updateAccount(account); + } + mXmppConnectionService.getAvatarService().clear(account); + mXmppConnectionService.updateConversationUi(); + mXmppConnectionService.updateAccountUi(); + } else { + Contact contact = account.getRoster().getContact(from); + contact.setAvatar(avatar); + mXmppConnectionService.getAvatarService().clear(contact); + mXmppConnectionService.updateConversationUi(); + mXmppConnectionService.updateRosterUi(); + } + } else if (mXmppConnectionService.isDataSaverDisabled()) { + mXmppConnectionService.fetchAvatar(account, avatar); + } + } + } else if ("http://jabber.org/protocol/nick".equals(node)) { + Element i = items.findChild("item"); + Element nick = i == null ? null : i.findChild("nick", "http://jabber.org/protocol/nick"); + if (nick != null && nick.getContent() != null) { + Contact contact = account.getRoster().getContact(from); + contact.setPresenceName(nick.getContent()); + mXmppConnectionService.getAvatarService().clear(account); + mXmppConnectionService.updateConversationUi(); + mXmppConnectionService.updateAccountUi(); + } + } else if (AxolotlService.PEP_DEVICE_LIST.equals(node)) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received PEP device list update from " + from + ", processing..."); + Element item = items.findChild("item"); + Set deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); + AxolotlService axolotlService = account.getAxolotlService(); + axolotlService.registerDevices(from, deviceIds); + mXmppConnectionService.updateAccountUi(); + } + } + + private boolean handleErrorMessage(Account account, MessagePacket packet) { + if (packet.getType() == MessagePacket.TYPE_ERROR) { + Jid from = packet.getFrom(); + if (from != null) { + Message message = mXmppConnectionService.markMessage(account, + from.toBareJid(), + packet.getId(), Message.STATUS_SEND_FAILED, extractErrorMessage(packet)); if (message != null) { if (message.getEncryption() == Message.ENCRYPTION_OTR) { message.getConversation().endOtrIfNeeded(); } - } - } - return true; - } - return false; - } - - @Override - public void onMessagePacketReceived(Account account, MessagePacket original) { - if (handleErrorMessage(account, original)) { - return; - } - final MessagePacket packet; - Long timestamp = null; - final boolean isForwarded; - boolean isCarbon = false; - String serverMsgId = null; - final Element fin = original.findChild("fin", "urn:xmpp:mam:0"); - if (fin != null) { - mXmppConnectionService.getMessageArchiveService().processFin(fin,original.getFrom()); - return; - } - final Element result = original.findChild("result","urn:xmpp:mam:0"); - final MessageArchiveService.Query query = result == null ? null : mXmppConnectionService.getMessageArchiveService().findQuery(result.getAttribute("queryid")); - if (query != null && query.validFrom(original.getFrom())) { - Pair f = original.getForwardedMessagePacket("result", "urn:xmpp:mam:0"); - if (f == null) { - return; - } - timestamp = f.second; - packet = f.first; - isForwarded = true; - serverMsgId = result.getAttribute("id"); - query.incrementMessageCount(); - } else if (query != null) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": received mam result from invalid sender"); - return; - } else if (original.fromServer(account)) { - Pair f; - f = original.getForwardedMessagePacket("received", "urn:xmpp:carbons:2"); - f = f == null ? original.getForwardedMessagePacket("sent", "urn:xmpp:carbons:2") : f; - packet = f != null ? f.first : original; - if (handleErrorMessage(account, packet)) { - return; - } - timestamp = f != null ? f.second : null; - isCarbon = f != null; - isForwarded = isCarbon; - } else { - packet = original; - isForwarded = false; - } - - if (timestamp == null) { - timestamp = AbstractParser.parseTimestamp(original,AbstractParser.parseTimestamp(packet)); - } - final String body = packet.getBody(); - final Element mucUserElement = packet.findChild("x", "http://jabber.org/protocol/muc#user"); - final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted"); - final Element replaceElement = packet.findChild("replace", "urn:xmpp:message-correct:0"); - final Element oob = packet.findChild("x", "jabber:x:oob"); - final boolean isOob = oob!= null && body != null && body.equals(oob.findChildContent("url")); - final String replacementId = replaceElement == null ? null : replaceElement.getAttribute("id"); - final Element axolotlEncrypted = packet.findChild(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX); - int status; - final Jid counterpart; - final Jid to = packet.getTo(); - final Jid from = packet.getFrom(); - final String remoteMsgId = packet.getId(); - boolean notify = false; - - if (from == null) { - Log.d(Config.LOGTAG,"no from in: "+packet.toString()); - return; - } - - boolean isTypeGroupChat = packet.getType() == MessagePacket.TYPE_GROUPCHAT; - boolean isProperlyAddressed = (to != null ) && (!to.isBareJid() || account.countPresences() == 0); - boolean isMucStatusMessage = from.isBareJid() && mucUserElement != null && mucUserElement.hasChild("status"); - if (packet.fromAccount(account)) { - status = Message.STATUS_SEND; - counterpart = to != null ? to : account.getJid(); - } else { - status = Message.STATUS_RECEIVED; - counterpart = from; - } - - Invite invite = extractInvite(packet); - if (invite != null && invite.execute(account)) { - return; - } - - if (!isTypeGroupChat - && query == null - && extractChatState(mXmppConnectionService.find(account, counterpart.toBareJid()), packet)) { - mXmppConnectionService.updateConversationUi(); - } - - if ((body != null || pgpEncrypted != null || axolotlEncrypted != null) && !isMucStatusMessage) { - Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, counterpart.toBareJid(), isTypeGroupChat, query); - final boolean conversationMultiMode = conversation.getMode() == Conversation.MODE_MULTI; - if (isTypeGroupChat) { - if (counterpart.getResourcepart().equals(conversation.getMucOptions().getActualNick())) { - status = Message.STATUS_SEND_RECEIVED; - isCarbon = true; //not really carbon but received from another resource - if (mXmppConnectionService.markMessage(conversation, remoteMsgId, status)) { - return; - } else if (remoteMsgId == null || Config.IGNORE_ID_REWRITE_IN_MUC) { - Message message = conversation.findSentMessageWithBody(packet.getBody()); - if (message != null) { - mXmppConnectionService.markMessage(message, status); - return; - } - } - } else { - status = Message.STATUS_RECEIVED; - } - } - final Message message; - if (body != null && body.startsWith("?OTR") && Config.supportOtr()) { - if (!isForwarded && !isTypeGroupChat && isProperlyAddressed && !conversationMultiMode) { - message = parseOtrChat(body, from, remoteMsgId, conversation); - if (message == null) { - return; - } - } else { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": ignoring OTR message from "+from+" isForwarded="+Boolean.toString(isForwarded)+", isProperlyAddressed="+Boolean.valueOf(isProperlyAddressed)); - message = new Message(conversation, body, Message.ENCRYPTION_NONE, status); - } - } else if (pgpEncrypted != null && Config.supportOpenPgp()) { - message = new Message(conversation, pgpEncrypted, Message.ENCRYPTION_PGP, status); - } else if (axolotlEncrypted != null && Config.supportOmemo()) { - Jid origin; - if (conversationMultiMode) { - final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart); - origin = getTrueCounterpart(query != null ? mucUserElement : null, fallback); - if (origin == null) { - Log.d(Config.LOGTAG,"axolotl message in non anonymous conference received"); - return; - } - } else { - origin = from; - } - message = parseAxolotlChat(axolotlEncrypted, origin, conversation, status); - if (message == null) { - return; - } - if (conversationMultiMode) { - message.setTrueCounterpart(origin); - } - } else { - message = new Message(conversation, body, Message.ENCRYPTION_NONE, status); - } - - if (serverMsgId == null) { + } + } + return true; + } + return false; + } + + @Override + public void onMessagePacketReceived(Account account, MessagePacket original) { + if (handleErrorMessage(account, original)) { + return; + } + final MessagePacket packet; + Long timestamp = null; + final boolean isForwarded; + boolean isCarbon = false; + String serverMsgId = null; + final Element fin = original.findChild("fin", "urn:xmpp:mam:0"); + if (fin != null) { + mXmppConnectionService.getMessageArchiveService().processFin(fin, original.getFrom()); + return; + } + final Element result = original.findChild("result", "urn:xmpp:mam:0"); + final MessageArchiveService.Query query = result == null ? null : mXmppConnectionService.getMessageArchiveService().findQuery(result.getAttribute("queryid")); + if (query != null && query.validFrom(original.getFrom())) { + Pair f = original.getForwardedMessagePacket("result", "urn:xmpp:mam:0"); + if (f == null) { + return; + } + timestamp = f.second; + packet = f.first; + isForwarded = true; + serverMsgId = result.getAttribute("id"); + query.incrementMessageCount(); + } else if (query != null) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": received mam result from invalid sender"); + return; + } else if (original.fromServer(account)) { + Pair f; + f = original.getForwardedMessagePacket("received", "urn:xmpp:carbons:2"); + f = f == null ? original.getForwardedMessagePacket("sent", "urn:xmpp:carbons:2") : f; + packet = f != null ? f.first : original; + if (handleErrorMessage(account, packet)) { + return; + } + timestamp = f != null ? f.second : null; + isCarbon = f != null; + isForwarded = isCarbon; + } else { + packet = original; + isForwarded = false; + } + + if (timestamp == null) { + timestamp = AbstractParser.parseTimestamp(original, AbstractParser.parseTimestamp(packet)); + } + final String body = packet.getBody(); + final Element mucUserElement = packet.findChild("x", "http://jabber.org/protocol/muc#user"); + final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted"); + final Element replaceElement = packet.findChild("replace", "urn:xmpp:message-correct:0"); + final Element oob = packet.findChild("x", "jabber:x:oob"); + final boolean isOob = oob != null && body != null && body.equals(oob.findChildContent("url")); + final String replacementId = replaceElement == null ? null : replaceElement.getAttribute("id"); + final Element axolotlEncrypted = packet.findChild(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX); + int status; + final Jid counterpart; + final Jid to = packet.getTo(); + final Jid from = packet.getFrom(); + final String remoteMsgId = packet.getId(); + boolean notify = false; + + if (from == null) { + Log.d(Config.LOGTAG, "no from in: " + packet.toString()); + return; + } + + boolean isTypeGroupChat = packet.getType() == MessagePacket.TYPE_GROUPCHAT; + boolean isProperlyAddressed = (to != null) && (!to.isBareJid() || account.countPresences() == 0); + boolean isMucStatusMessage = from.isBareJid() && mucUserElement != null && mucUserElement.hasChild("status"); + if (packet.fromAccount(account)) { + status = Message.STATUS_SEND; + counterpart = to != null ? to : account.getJid(); + } else { + status = Message.STATUS_RECEIVED; + counterpart = from; + } + + Invite invite = extractInvite(packet); + if (invite != null && invite.execute(account)) { + return; + } + + if (!isTypeGroupChat + && query == null + && extractChatState(mXmppConnectionService.find(account, counterpart.toBareJid()), packet)) { + mXmppConnectionService.updateConversationUi(); + } + + if ((body != null || pgpEncrypted != null || axolotlEncrypted != null) && !isMucStatusMessage) { + Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, counterpart.toBareJid(), isTypeGroupChat, query); + final boolean conversationMultiMode = conversation.getMode() == Conversation.MODE_MULTI; + if (isTypeGroupChat) { + if (counterpart.getResourcepart().equals(conversation.getMucOptions().getActualNick())) { + status = Message.STATUS_SEND_RECEIVED; + isCarbon = true; //not really carbon but received from another resource + if (mXmppConnectionService.markMessage(conversation, remoteMsgId, status)) { + return; + } else if (remoteMsgId == null || Config.IGNORE_ID_REWRITE_IN_MUC) { + Message message = conversation.findSentMessageWithBody(packet.getBody()); + if (message != null) { + mXmppConnectionService.markMessage(message, status); + return; + } + } + } else { + status = Message.STATUS_RECEIVED; + } + } + final Message message; + if (body != null && body.startsWith("?OTR") && Config.supportOtr()) { + if (!isForwarded && !isTypeGroupChat && isProperlyAddressed && !conversationMultiMode) { + message = parseOtrChat(body, from, remoteMsgId, conversation); + if (message == null) { + return; + } + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": ignoring OTR message from " + from + " isForwarded=" + Boolean.toString(isForwarded) + ", isProperlyAddressed=" + Boolean.valueOf(isProperlyAddressed)); + message = new Message(conversation, body, Message.ENCRYPTION_NONE, status); + } + } else if (pgpEncrypted != null && Config.supportOpenPgp()) { + message = new Message(conversation, pgpEncrypted, Message.ENCRYPTION_PGP, status); + } else if (axolotlEncrypted != null && Config.supportOmemo()) { + Jid origin; + if (conversationMultiMode) { + final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart); + origin = getTrueCounterpart(query != null ? mucUserElement : null, fallback); + if (origin == null) { + Log.d(Config.LOGTAG, "axolotl message in non anonymous conference received"); + return; + } + } else { + origin = from; + } + message = parseAxolotlChat(axolotlEncrypted, origin, conversation, status); + if (message == null) { + return; + } + if (conversationMultiMode) { + message.setTrueCounterpart(origin); + } + } else { + message = new Message(conversation, body, Message.ENCRYPTION_NONE, status); + } + + if (serverMsgId == null) { final Jid by; final boolean safeToExtract; if (isTypeGroupChat) { @@ -443,154 +444,154 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece if (safeToExtract) { serverMsgId = extractStanzaId(packet, by); } - } - - message.setCounterpart(counterpart); - message.setRemoteMsgId(remoteMsgId); - message.setServerMsgId(serverMsgId); - message.setCarbon(isCarbon); - message.setTime(timestamp); - message.setOob(isOob); - message.markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0"); - if (conversationMultiMode) { - final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart); - Jid trueCounterpart; - if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { - trueCounterpart = message.getTrueCounterpart(); - } else if (Config.PARSE_REAL_JID_FROM_MUC_MAM) { - trueCounterpart = getTrueCounterpart(query != null ? mucUserElement : null, fallback); - } else { - trueCounterpart = fallback; - } - if (trueCounterpart != null && trueCounterpart.toBareJid().equals(account.getJid().toBareJid())) { - status = isTypeGroupChat ? Message.STATUS_SEND_RECEIVED : Message.STATUS_SEND; - } - message.setStatus(status); - message.setTrueCounterpart(trueCounterpart); - if (!isTypeGroupChat) { - message.setType(Message.TYPE_PRIVATE); - } - } else { - updateLastseen(account, from); - } - - if (replacementId != null && mXmppConnectionService.allowMessageCorrection()) { - Message replacedMessage = conversation.findMessageWithRemoteIdAndCounterpart(replacementId, - counterpart, - message.getStatus() == Message.STATUS_RECEIVED, - message.isCarbon()); - if (replacedMessage != null) { - final boolean fingerprintsMatch = replacedMessage.getFingerprint() == null - || replacedMessage.getFingerprint().equals(message.getFingerprint()); - final boolean trueCountersMatch = replacedMessage.getTrueCounterpart() != null - && replacedMessage.getTrueCounterpart().equals(message.getTrueCounterpart()); - if (fingerprintsMatch && (trueCountersMatch || !conversationMultiMode)) { - Log.d(Config.LOGTAG, "replaced message '" + replacedMessage.getBody() + "' with '" + message.getBody() + "'"); - synchronized (replacedMessage) { - final String uuid = replacedMessage.getUuid(); - replacedMessage.setUuid(UUID.randomUUID().toString()); - replacedMessage.setBody(message.getBody()); - replacedMessage.setEdited(replacedMessage.getRemoteMsgId()); - replacedMessage.setRemoteMsgId(remoteMsgId); - replacedMessage.setEncryption(message.getEncryption()); - if (replacedMessage.getStatus() == Message.STATUS_RECEIVED) { - replacedMessage.markUnread(); - } - mXmppConnectionService.updateMessage(replacedMessage, uuid); - mXmppConnectionService.getNotificationService().updateNotification(false); - if (mXmppConnectionService.confirmMessages() && remoteMsgId != null && !isForwarded && !isTypeGroupChat) { - sendMessageReceipts(account, packet); - } - if (replacedMessage.getEncryption() == Message.ENCRYPTION_PGP) { - conversation.getAccount().getPgpDecryptionService().discard(replacedMessage); - conversation.getAccount().getPgpDecryptionService().decrypt(replacedMessage, false); - } - } - return; - } else { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": received message correction but verification didn't check out"); - } - } - } - - boolean checkForDuplicates = query != null - || (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()); - return; - } - - if (query != null && query.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) { - conversation.prepend(message); - } else { - conversation.add(message); - } - - if (query == null || query.getWith() == null) { //either no mam or catchup - if (status == Message.STATUS_SEND || status == Message.STATUS_SEND_RECEIVED) { - mXmppConnectionService.markRead(conversation); - if (query == null) { - activateGracePeriod(account); - } - } else { - message.markUnread(); - notify = true; - } - } - - if (message.getEncryption() == Message.ENCRYPTION_PGP) { - notify = conversation.getAccount().getPgpDecryptionService().decrypt(message, notify); - } - - if (query == null) { - mXmppConnectionService.updateConversationUi(); - } + } + + message.setCounterpart(counterpart); + message.setRemoteMsgId(remoteMsgId); + message.setServerMsgId(serverMsgId); + message.setCarbon(isCarbon); + message.setTime(timestamp); + message.setOob(isOob); + message.markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0"); + if (conversationMultiMode) { + final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart); + Jid trueCounterpart; + if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { + trueCounterpart = message.getTrueCounterpart(); + } else if (Config.PARSE_REAL_JID_FROM_MUC_MAM) { + trueCounterpart = getTrueCounterpart(query != null ? mucUserElement : null, fallback); + } else { + trueCounterpart = fallback; + } + if (trueCounterpart != null && trueCounterpart.toBareJid().equals(account.getJid().toBareJid())) { + status = isTypeGroupChat ? Message.STATUS_SEND_RECEIVED : Message.STATUS_SEND; + } + message.setStatus(status); + message.setTrueCounterpart(trueCounterpart); + if (!isTypeGroupChat) { + message.setType(Message.TYPE_PRIVATE); + } + } else { + updateLastseen(account, from); + } + + if (replacementId != null && mXmppConnectionService.allowMessageCorrection()) { + Message replacedMessage = conversation.findMessageWithRemoteIdAndCounterpart(replacementId, + counterpart, + message.getStatus() == Message.STATUS_RECEIVED, + message.isCarbon()); + if (replacedMessage != null) { + final boolean fingerprintsMatch = replacedMessage.getFingerprint() == null + || replacedMessage.getFingerprint().equals(message.getFingerprint()); + final boolean trueCountersMatch = replacedMessage.getTrueCounterpart() != null + && replacedMessage.getTrueCounterpart().equals(message.getTrueCounterpart()); + if (fingerprintsMatch && (trueCountersMatch || !conversationMultiMode)) { + Log.d(Config.LOGTAG, "replaced message '" + replacedMessage.getBody() + "' with '" + message.getBody() + "'"); + synchronized (replacedMessage) { + final String uuid = replacedMessage.getUuid(); + replacedMessage.setUuid(UUID.randomUUID().toString()); + replacedMessage.setBody(message.getBody()); + replacedMessage.setEdited(replacedMessage.getRemoteMsgId()); + replacedMessage.setRemoteMsgId(remoteMsgId); + replacedMessage.setEncryption(message.getEncryption()); + if (replacedMessage.getStatus() == Message.STATUS_RECEIVED) { + replacedMessage.markUnread(); + } + mXmppConnectionService.updateMessage(replacedMessage, uuid); + mXmppConnectionService.getNotificationService().updateNotification(false); + if (mXmppConnectionService.confirmMessages() && remoteMsgId != null && !isForwarded && !isTypeGroupChat) { + sendMessageReceipts(account, packet); + } + if (replacedMessage.getEncryption() == Message.ENCRYPTION_PGP) { + conversation.getAccount().getPgpDecryptionService().discard(replacedMessage); + conversation.getAccount().getPgpDecryptionService().decrypt(replacedMessage, false); + } + } + return; + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": received message correction but verification didn't check out"); + } + } + } + + boolean checkForDuplicates = query != null + || (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()); + return; + } + + if (query != null && query.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) { + conversation.prepend(message); + } else { + conversation.add(message); + } + + if (query == null || query.getWith() == null) { //either no mam or catchup + if (status == Message.STATUS_SEND || status == Message.STATUS_SEND_RECEIVED) { + mXmppConnectionService.markRead(conversation); + if (query == null) { + activateGracePeriod(account); + } + } else { + message.markUnread(); + notify = true; + } + } + + if (message.getEncryption() == Message.ENCRYPTION_PGP) { + notify = conversation.getAccount().getPgpDecryptionService().decrypt(message, notify); + } + + if (query == null) { + mXmppConnectionService.updateConversationUi(); + } if (mXmppConnectionService.confirmMessages() && message.trusted() && remoteMsgId != null && !isForwarded && !isTypeGroupChat) { - sendMessageReceipts(account, packet); - } - - if (message.getStatus() == Message.STATUS_RECEIVED - && conversation.getOtrSession() != null - && !conversation.getOtrSession().getSessionID().getUserID() - .equals(message.getCounterpart().getResourcepart())) { - conversation.endOtrIfNeeded(); - } - - if (message.getEncryption() == Message.ENCRYPTION_NONE || mXmppConnectionService.saveEncryptedMessages()) { - mXmppConnectionService.databaseBackend.createMessage(message); - } - final HttpConnectionManager manager = this.mXmppConnectionService.getHttpConnectionManager(); - if (message.trusted() && message.treatAsDownloadable() != Message.Decision.NEVER && manager.getAutoAcceptFileSize() > 0) { - manager.createNewDownloadConnection(message); - } else if (notify) { - if (query == null) { - mXmppConnectionService.getNotificationService().push(message); - } else if (query.getWith() == null) { // mam catchup - mXmppConnectionService.getNotificationService().pushFromBacklog(message); - } - } - } else if (!packet.hasChild("body")){ //no body - Conversation conversation = mXmppConnectionService.find(account, from.toBareJid()); - if (isTypeGroupChat) { - if (packet.hasChild("subject")) { - if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) { - conversation.setHasMessagesLeftOnServer(conversation.countMessages() > 0); - String subject = packet.findChildContent("subject"); - conversation.getMucOptions().setSubject(subject); - final Bookmark bookmark = conversation.getBookmark(); - if (bookmark != null && bookmark.getBookmarkName() == null) { - if (bookmark.setBookmarkName(subject)) { - mXmppConnectionService.pushBookmarks(account); - } - } - mXmppConnectionService.updateConversationUi(); - return; - } - } - } - if (conversation != null && mucUserElement != null && from.isBareJid()) { + sendMessageReceipts(account, packet); + } + + if (message.getStatus() == Message.STATUS_RECEIVED + && conversation.getOtrSession() != null + && !conversation.getOtrSession().getSessionID().getUserID() + .equals(message.getCounterpart().getResourcepart())) { + conversation.endOtrIfNeeded(); + } + + if (message.getEncryption() == Message.ENCRYPTION_NONE || mXmppConnectionService.saveEncryptedMessages()) { + mXmppConnectionService.databaseBackend.createMessage(message); + } + final HttpConnectionManager manager = this.mXmppConnectionService.getHttpConnectionManager(); + if (message.trusted() && message.treatAsDownloadable() != Message.Decision.NEVER && manager.getAutoAcceptFileSize() > 0) { + manager.createNewDownloadConnection(message); + } else if (notify) { + if (query == null) { + mXmppConnectionService.getNotificationService().push(message); + } else if (query.getWith() == null) { // mam catchup + mXmppConnectionService.getNotificationService().pushFromBacklog(message); + } + } + } else if (!packet.hasChild("body")) { //no body + Conversation conversation = mXmppConnectionService.find(account, from.toBareJid()); + if (isTypeGroupChat) { + if (packet.hasChild("subject")) { + if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) { + conversation.setHasMessagesLeftOnServer(conversation.countMessages() > 0); + String subject = packet.findChildContent("subject"); + conversation.getMucOptions().setSubject(subject); + final Bookmark bookmark = conversation.getBookmark(); + if (bookmark != null && bookmark.getBookmarkName() == null) { + if (bookmark.setBookmarkName(subject)) { + mXmppConnectionService.pushBookmarks(account); + } + } + mXmppConnectionService.updateConversationUi(); + return; + } + } + } + if (conversation != null && mucUserElement != null && from.isBareJid()) { for (Element child : mucUserElement.getChildren()) { if ("status".equals(child.getName())) { try { @@ -598,92 +599,91 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece if ((code >= 170 && code <= 174) || (code >= 102 && code <= 104)) { mXmppConnectionService.fetchConferenceConfiguration(conversation); break; - } + } } catch (Exception e) { //ignored } } else if ("item".equals(child.getName())) { - MucOptions.User user = AbstractParser.parseItem(conversation,child); - Log.d(Config.LOGTAG,account.getJid()+": changing affiliation for " + user.getRealJid()+" to " + user.getAffiliation() + " in " + conversation.getJid().toBareJid()); + MucOptions.User user = AbstractParser.parseItem(conversation, child); + Log.d(Config.LOGTAG, account.getJid() + ": changing affiliation for " + user.getRealJid() + " to " + user.getAffiliation() + " in " + conversation.getJid().toBareJid()); if (!user.realJidMatchesAccount()) { conversation.getMucOptions().updateUser(user); mXmppConnectionService.getAvatarService().clear(conversation); mXmppConnectionService.updateMucRosterUi(); mXmppConnectionService.updateConversationUi(); - } - } - } - } - } - - - - Element received = packet.findChild("received", "urn:xmpp:chat-markers:0"); - if (received == null) { - received = packet.findChild("received", "urn:xmpp:receipts"); - } - if (received != null && !packet.fromAccount(account)) { - mXmppConnectionService.markMessage(account, from.toBareJid(), received.getAttribute("id"), Message.STATUS_SEND_RECEIVED); - } - Element displayed = packet.findChild("displayed", "urn:xmpp:chat-markers:0"); - if (displayed != null) { - if (packet.fromAccount(account)) { - Conversation conversation = mXmppConnectionService.find(account,counterpart.toBareJid()); - if (conversation != null) { - mXmppConnectionService.markRead(conversation); - } - } else { - final Message displayedMessage = mXmppConnectionService.markMessage(account, from.toBareJid(), displayed.getAttribute("id"), Message.STATUS_SEND_DISPLAYED); - Message message = displayedMessage == null ? null : displayedMessage.prev(); - while (message != null - && message.getStatus() == Message.STATUS_SEND_RECEIVED - && message.getTimeSent() < displayedMessage.getTimeSent()) { - mXmppConnectionService.markMessage(message, Message.STATUS_SEND_DISPLAYED); - message = message.prev(); - } - } - } - - Element event = packet.findChild("event", "http://jabber.org/protocol/pubsub#event"); - if (event != null) { - parseEvent(event, from, account); - } - - String nick = packet.findChildContent("nick", "http://jabber.org/protocol/nick"); - if (nick != null) { - Contact contact = account.getRoster().getContact(from); - contact.setPresenceName(nick); - } - } - - private static Jid getTrueCounterpart(Element mucUserElement, Jid fallback) { - final Element item = mucUserElement == null ? null : mucUserElement.findChild("item"); - Jid result = item == null ? null : item.getAttributeAsJid("jid"); - return result != null ? result : fallback; - } - - private void sendMessageReceipts(Account account, MessagePacket packet) { - ArrayList receiptsNamespaces = new ArrayList<>(); - if (packet.hasChild("markable", "urn:xmpp:chat-markers:0")) { - receiptsNamespaces.add("urn:xmpp:chat-markers:0"); - } - if (packet.hasChild("request", "urn:xmpp:receipts")) { - receiptsNamespaces.add("urn:xmpp:receipts"); - } - if (receiptsNamespaces.size() > 0) { - MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account, - packet, - receiptsNamespaces, - packet.getType()); - mXmppConnectionService.sendMessagePacket(account, receipt); - } - } - - private static SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss"); - - private void activateGracePeriod(Account account) { - long duration = mXmppConnectionService.getPreferences().getLong("race_period_length", 144) * 1000; - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": activating grace period till "+TIME_FORMAT.format(new Date(System.currentTimeMillis() + duration))); - account.activateGracePeriod(duration); - } + } + } + } + } + } + + + Element received = packet.findChild("received", "urn:xmpp:chat-markers:0"); + if (received == null) { + received = packet.findChild("received", "urn:xmpp:receipts"); + } + if (received != null && !packet.fromAccount(account)) { + mXmppConnectionService.markMessage(account, from.toBareJid(), received.getAttribute("id"), Message.STATUS_SEND_RECEIVED); + } + Element displayed = packet.findChild("displayed", "urn:xmpp:chat-markers:0"); + if (displayed != null) { + if (packet.fromAccount(account)) { + Conversation conversation = mXmppConnectionService.find(account, counterpart.toBareJid()); + if (conversation != null) { + mXmppConnectionService.markRead(conversation); + } + } else { + final Message displayedMessage = mXmppConnectionService.markMessage(account, from.toBareJid(), displayed.getAttribute("id"), Message.STATUS_SEND_DISPLAYED); + Message message = displayedMessage == null ? null : displayedMessage.prev(); + while (message != null + && message.getStatus() == Message.STATUS_SEND_RECEIVED + && message.getTimeSent() < displayedMessage.getTimeSent()) { + mXmppConnectionService.markMessage(message, Message.STATUS_SEND_DISPLAYED); + message = message.prev(); + } + } + } + + Element event = packet.findChild("event", "http://jabber.org/protocol/pubsub#event"); + if (event != null) { + parseEvent(event, from, account); + } + + String nick = packet.findChildContent("nick", "http://jabber.org/protocol/nick"); + if (nick != null) { + Contact contact = account.getRoster().getContact(from); + contact.setPresenceName(nick); + } + } + + private static Jid getTrueCounterpart(Element mucUserElement, Jid fallback) { + final Element item = mucUserElement == null ? null : mucUserElement.findChild("item"); + Jid result = item == null ? null : item.getAttributeAsJid("jid"); + return result != null ? result : fallback; + } + + private void sendMessageReceipts(Account account, MessagePacket packet) { + ArrayList receiptsNamespaces = new ArrayList<>(); + if (packet.hasChild("markable", "urn:xmpp:chat-markers:0")) { + receiptsNamespaces.add("urn:xmpp:chat-markers:0"); + } + if (packet.hasChild("request", "urn:xmpp:receipts")) { + receiptsNamespaces.add("urn:xmpp:receipts"); + } + if (receiptsNamespaces.size() > 0) { + MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account, + packet, + receiptsNamespaces, + packet.getType()); + mXmppConnectionService.sendMessagePacket(account, receipt); + } + } + + private static SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss"); + + private void activateGracePeriod(Account account) { + long duration = mXmppConnectionService.getPreferences().getLong("race_period_length", 144) * 1000; + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": activating grace period till " + TIME_FORMAT.format(new Date(System.currentTimeMillis() + duration))); + account.activateGracePeriod(duration); + } } diff --git a/src/main/java/de/pixart/messenger/parser/PresenceParser.java b/src/main/java/de/pixart/messenger/parser/PresenceParser.java index 50f13150b..9ba45b201 100644 --- a/src/main/java/de/pixart/messenger/parser/PresenceParser.java +++ b/src/main/java/de/pixart/messenger/parser/PresenceParser.java @@ -24,170 +24,170 @@ import de.pixart.messenger.xmpp.pep.Avatar; import de.pixart.messenger.xmpp.stanzas.PresencePacket; public class PresenceParser extends AbstractParser implements - OnPresencePacketReceived { + OnPresencePacketReceived { - public PresenceParser(XmppConnectionService service) { - super(service); - } + public PresenceParser(XmppConnectionService service) { + super(service); + } - public void parseConferencePresence(PresencePacket packet, Account account) { - final Conversation conversation = packet.getFrom() == null ? null : mXmppConnectionService.find(account, packet.getFrom().toBareJid()); - if (conversation != null) { - final MucOptions mucOptions = conversation.getMucOptions(); - boolean before = mucOptions.online(); - int count = mucOptions.getUserCount(); - final List tileUserBefore = mucOptions.getUsers(5); - processConferencePresence(packet, conversation); - final List tileUserAfter = mucOptions.getUsers(5); - if (!tileUserAfter.equals(tileUserBefore)) { - mXmppConnectionService.getAvatarService().clear(mucOptions); - } - if (before != mucOptions.online() || (mucOptions.online() && count != mucOptions.getUserCount())) { - mXmppConnectionService.updateConversationUi(); - } else if (mucOptions.online()) { - mXmppConnectionService.updateMucRosterUi(); - } - } - } + public void parseConferencePresence(PresencePacket packet, Account account) { + final Conversation conversation = packet.getFrom() == null ? null : mXmppConnectionService.find(account, packet.getFrom().toBareJid()); + if (conversation != null) { + final MucOptions mucOptions = conversation.getMucOptions(); + boolean before = mucOptions.online(); + int count = mucOptions.getUserCount(); + final List tileUserBefore = mucOptions.getUsers(5); + processConferencePresence(packet, conversation); + final List tileUserAfter = mucOptions.getUsers(5); + if (!tileUserAfter.equals(tileUserBefore)) { + mXmppConnectionService.getAvatarService().clear(mucOptions); + } + if (before != mucOptions.online() || (mucOptions.online() && count != mucOptions.getUserCount())) { + mXmppConnectionService.updateConversationUi(); + } else if (mucOptions.online()) { + mXmppConnectionService.updateMucRosterUi(); + } + } + } private void processConferencePresence(PresencePacket packet, Conversation conversation) { MucOptions mucOptions = conversation.getMucOptions(); - final Jid from = packet.getFrom(); - if (!from.isBareJid()) { - final String type = packet.getAttribute("type"); - final Element x = packet.findChild("x", "http://jabber.org/protocol/muc#user"); - Avatar avatar = Avatar.parsePresence(packet.findChild("x", "vcard-temp:x:update")); - final List codes = getStatusCodes(x); - if (type == null) { - if (x != null) { - Element item = x.findChild("item"); - if (item != null && !from.isBareJid()) { - mucOptions.setError(MucOptions.Error.NONE); + final Jid from = packet.getFrom(); + if (!from.isBareJid()) { + final String type = packet.getAttribute("type"); + final Element x = packet.findChild("x", "http://jabber.org/protocol/muc#user"); + Avatar avatar = Avatar.parsePresence(packet.findChild("x", "vcard-temp:x:update")); + final List codes = getStatusCodes(x); + if (type == null) { + if (x != null) { + Element item = x.findChild("item"); + if (item != null && !from.isBareJid()) { + mucOptions.setError(MucOptions.Error.NONE); MucOptions.User user = parseItem(conversation, item, from); - if (codes.contains(MucOptions.STATUS_CODE_SELF_PRESENCE) || packet.getFrom().equals(mucOptions.getConversation().getJid())) { - mucOptions.setOnline(); - mucOptions.setSelf(user); - if (mucOptions.mNickChangingInProgress) { - if (mucOptions.onRenameListener != null) { - mucOptions.onRenameListener.onSuccess(); - } - mucOptions.mNickChangingInProgress = false; - } - } else { - mucOptions.updateUser(user); - } - if (codes.contains(MucOptions.STATUS_CODE_ROOM_CREATED) && mucOptions.autoPushConfiguration()) { - Log.d(Config.LOGTAG,mucOptions.getAccount().getJid().toBareJid() - +": room '" - +mucOptions.getConversation().getJid().toBareJid() - +"' created. pushing default configuration"); - mXmppConnectionService.pushConferenceConfiguration(mucOptions.getConversation(), - IqGenerator.defaultRoomConfiguration(), - null); - } - if (mXmppConnectionService.getPgpEngine() != null) { - Element signed = packet.findChild("x", "jabber:x:signed"); - if (signed != null) { - Element status = packet.findChild("status"); - String msg = status == null ? "" : status.getContent(); - long keyId = mXmppConnectionService.getPgpEngine().fetchKeyId(mucOptions.getAccount(), msg, signed.getContent()); - if (keyId != 0) { - user.setPgpKeyId(keyId); - } - } - } - if (avatar != null) { - avatar.owner = from; - if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) { - if (user.setAvatar(avatar)) { - mXmppConnectionService.getAvatarService().clear(user); - } - } else if (mXmppConnectionService.isDataSaverDisabled()) { - mXmppConnectionService.fetchAvatar(mucOptions.getAccount(), avatar); - } - } - } - } - } else if (type.equals("unavailable")) { - if (codes.contains(MucOptions.STATUS_CODE_SELF_PRESENCE) || - packet.getFrom().equals(mucOptions.getConversation().getJid())) { - if (codes.contains(MucOptions.STATUS_CODE_CHANGED_NICK)) { - mucOptions.mNickChangingInProgress = true; - } else if (codes.contains(MucOptions.STATUS_CODE_KICKED)) { - mucOptions.setError(MucOptions.Error.KICKED); - } else if (codes.contains(MucOptions.STATUS_CODE_BANNED)) { - mucOptions.setError(MucOptions.Error.BANNED); - } else if (codes.contains(MucOptions.STATUS_CODE_LOST_MEMBERSHIP)) { - mucOptions.setError(MucOptions.Error.MEMBERS_ONLY); - } else if (codes.contains(MucOptions.STATUS_CODE_AFFILIATION_CHANGE)) { - mucOptions.setError(MucOptions.Error.MEMBERS_ONLY); - } else if (codes.contains(MucOptions.STATUS_CODE_SHUTDOWN)) { - mucOptions.setError(MucOptions.Error.SHUTDOWN); - } else { - mucOptions.setError(MucOptions.Error.UNKNOWN); - Log.d(Config.LOGTAG, "unknown error in conference: " + packet); - } - } else if (!from.isBareJid()){ + if (codes.contains(MucOptions.STATUS_CODE_SELF_PRESENCE) || packet.getFrom().equals(mucOptions.getConversation().getJid())) { + mucOptions.setOnline(); + mucOptions.setSelf(user); + if (mucOptions.mNickChangingInProgress) { + if (mucOptions.onRenameListener != null) { + mucOptions.onRenameListener.onSuccess(); + } + mucOptions.mNickChangingInProgress = false; + } + } else { + mucOptions.updateUser(user); + } + if (codes.contains(MucOptions.STATUS_CODE_ROOM_CREATED) && mucOptions.autoPushConfiguration()) { + Log.d(Config.LOGTAG, mucOptions.getAccount().getJid().toBareJid() + + ": room '" + + mucOptions.getConversation().getJid().toBareJid() + + "' created. pushing default configuration"); + mXmppConnectionService.pushConferenceConfiguration(mucOptions.getConversation(), + IqGenerator.defaultRoomConfiguration(), + null); + } + if (mXmppConnectionService.getPgpEngine() != null) { + Element signed = packet.findChild("x", "jabber:x:signed"); + if (signed != null) { + Element status = packet.findChild("status"); + String msg = status == null ? "" : status.getContent(); + long keyId = mXmppConnectionService.getPgpEngine().fetchKeyId(mucOptions.getAccount(), msg, signed.getContent()); + if (keyId != 0) { + user.setPgpKeyId(keyId); + } + } + } + if (avatar != null) { + avatar.owner = from; + if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) { + if (user.setAvatar(avatar)) { + mXmppConnectionService.getAvatarService().clear(user); + } + } else if (mXmppConnectionService.isDataSaverDisabled()) { + mXmppConnectionService.fetchAvatar(mucOptions.getAccount(), avatar); + } + } + } + } + } else if (type.equals("unavailable")) { + if (codes.contains(MucOptions.STATUS_CODE_SELF_PRESENCE) || + packet.getFrom().equals(mucOptions.getConversation().getJid())) { + if (codes.contains(MucOptions.STATUS_CODE_CHANGED_NICK)) { + mucOptions.mNickChangingInProgress = true; + } else if (codes.contains(MucOptions.STATUS_CODE_KICKED)) { + mucOptions.setError(MucOptions.Error.KICKED); + } else if (codes.contains(MucOptions.STATUS_CODE_BANNED)) { + mucOptions.setError(MucOptions.Error.BANNED); + } else if (codes.contains(MucOptions.STATUS_CODE_LOST_MEMBERSHIP)) { + mucOptions.setError(MucOptions.Error.MEMBERS_ONLY); + } else if (codes.contains(MucOptions.STATUS_CODE_AFFILIATION_CHANGE)) { + mucOptions.setError(MucOptions.Error.MEMBERS_ONLY); + } else if (codes.contains(MucOptions.STATUS_CODE_SHUTDOWN)) { + mucOptions.setError(MucOptions.Error.SHUTDOWN); + } else { + mucOptions.setError(MucOptions.Error.UNKNOWN); + Log.d(Config.LOGTAG, "unknown error in conference: " + packet); + } + } else if (!from.isBareJid()) { Element item = x.findChild("item"); if (item != null) { mucOptions.updateUser(parseItem(conversation, item, from)); } - MucOptions.User user = mucOptions.deleteUser(from); - if (user != null) { - mXmppConnectionService.getAvatarService().clear(user); - } - } - } else if (type.equals("error")) { - Element error = packet.findChild("error"); - if (error != null && error.hasChild("conflict")) { - if (mucOptions.online()) { - if (mucOptions.onRenameListener != null) { - mucOptions.onRenameListener.onFailure(); - } - } else { - mucOptions.setError(MucOptions.Error.NICK_IN_USE); - } - } else if (error != null && error.hasChild("not-authorized")) { - mucOptions.setError(MucOptions.Error.PASSWORD_REQUIRED); - } else if (error != null && error.hasChild("forbidden")) { - mucOptions.setError(MucOptions.Error.BANNED); - } else if (error != null && error.hasChild("registration-required")) { - mucOptions.setError(MucOptions.Error.MEMBERS_ONLY); - } - } - } - } + MucOptions.User user = mucOptions.deleteUser(from); + if (user != null) { + mXmppConnectionService.getAvatarService().clear(user); + } + } + } else if (type.equals("error")) { + Element error = packet.findChild("error"); + if (error != null && error.hasChild("conflict")) { + if (mucOptions.online()) { + if (mucOptions.onRenameListener != null) { + mucOptions.onRenameListener.onFailure(); + } + } else { + mucOptions.setError(MucOptions.Error.NICK_IN_USE); + } + } else if (error != null && error.hasChild("not-authorized")) { + mucOptions.setError(MucOptions.Error.PASSWORD_REQUIRED); + } else if (error != null && error.hasChild("forbidden")) { + mucOptions.setError(MucOptions.Error.BANNED); + } else if (error != null && error.hasChild("registration-required")) { + mucOptions.setError(MucOptions.Error.MEMBERS_ONLY); + } + } + } + } - private static List getStatusCodes(Element x) { - List codes = new ArrayList<>(); - if (x != null) { - for (Element child : x.getChildren()) { - if (child.getName().equals("status")) { - String code = child.getAttribute("code"); - if (code != null) { - codes.add(code); - } - } - } - } - return codes; - } + private static List getStatusCodes(Element x) { + List codes = new ArrayList<>(); + if (x != null) { + for (Element child : x.getChildren()) { + if (child.getName().equals("status")) { + String code = child.getAttribute("code"); + if (code != null) { + codes.add(code); + } + } + } + } + return codes; + } - public void parseContactPresence(final PresencePacket packet, final Account account) { - final PresenceGenerator mPresenceGenerator = mXmppConnectionService.getPresenceGenerator(); - final Jid from = packet.getFrom(); - if (from == null || from.equals(account.getJid())) { - return; - } - final String type = packet.getAttribute("type"); - final Contact contact = account.getRoster().getContact(from); - if (type == null) { - final String resource = from.isBareJid() ? "" : from.getResourcepart(); - contact.setPresenceName(packet.findChildContent("nick", "http://jabber.org/protocol/nick")); - Avatar avatar = Avatar.parsePresence(packet.findChild("x", "vcard-temp:x:update")); + public void parseContactPresence(final PresencePacket packet, final Account account) { + final PresenceGenerator mPresenceGenerator = mXmppConnectionService.getPresenceGenerator(); + final Jid from = packet.getFrom(); + if (from == null || from.equals(account.getJid())) { + return; + } + final String type = packet.getAttribute("type"); + final Contact contact = account.getRoster().getContact(from); + if (type == null) { + final String resource = from.isBareJid() ? "" : from.getResourcepart(); + contact.setPresenceName(packet.findChildContent("nick", "http://jabber.org/protocol/nick")); + Avatar avatar = Avatar.parsePresence(packet.findChild("x", "vcard-temp:x:update")); if (avatar != null && (!contact.isSelf() || account.getAvatar() == null)) { avatar.owner = from.toBareJid(); - if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) { + if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) { if (avatar.owner.equals(account.getJid().toBareJid())) { account.setAvatar(avatar.getFilename()); mXmppConnectionService.databaseBackend.updateAccount(account); @@ -197,86 +197,86 @@ public class PresenceParser extends AbstractParser implements } else if (contact.setAvatar(avatar)) { mXmppConnectionService.getAvatarService().clear(contact); mXmppConnectionService.updateConversationUi(); - mXmppConnectionService.updateRosterUi(); - } - } else if (mXmppConnectionService.isDataSaverDisabled()){ - mXmppConnectionService.fetchAvatar(account, avatar); - } - } - int sizeBefore = contact.getPresences().size(); + mXmppConnectionService.updateRosterUi(); + } + } else if (mXmppConnectionService.isDataSaverDisabled()) { + mXmppConnectionService.fetchAvatar(account, avatar); + } + } + int sizeBefore = contact.getPresences().size(); - final String show = packet.findChildContent("show"); - final Element caps = packet.findChild("c", "http://jabber.org/protocol/caps"); - final String message = packet.findChildContent("status"); - final Presence presence = Presence.parse(show, caps, message); - contact.updatePresence(resource, presence); - if (presence.hasCaps()) { - mXmppConnectionService.fetchCaps(account, from, presence); - } + final String show = packet.findChildContent("show"); + final Element caps = packet.findChild("c", "http://jabber.org/protocol/caps"); + final String message = packet.findChildContent("status"); + final Presence presence = Presence.parse(show, caps, message); + contact.updatePresence(resource, presence); + if (presence.hasCaps()) { + mXmppConnectionService.fetchCaps(account, from, presence); + } - final Element idle = packet.findChild("idle","urn:xmpp:idle:1"); - if (idle != null) { - contact.flagInactive(); - String since = idle.getAttribute("since"); - try { - contact.setLastseen(AbstractParser.parseTimestamp(since)); - } catch (NullPointerException | ParseException e) { - contact.setLastseen(System.currentTimeMillis()); - } - } else { - contact.flagActive(); - contact.setLastseen(AbstractParser.parseTimestamp(packet)); - } + final Element idle = packet.findChild("idle", "urn:xmpp:idle:1"); + if (idle != null) { + contact.flagInactive(); + String since = idle.getAttribute("since"); + try { + contact.setLastseen(AbstractParser.parseTimestamp(since)); + } catch (NullPointerException | ParseException e) { + contact.setLastseen(System.currentTimeMillis()); + } + } else { + contact.flagActive(); + contact.setLastseen(AbstractParser.parseTimestamp(packet)); + } - PgpEngine pgp = mXmppConnectionService.getPgpEngine(); - Element x = packet.findChild("x", "jabber:x:signed"); - if (pgp != null && x != null) { - Element status = packet.findChild("status"); - String msg = status != null ? status.getContent() : ""; - contact.setPgpKeyId(pgp.fetchKeyId(account, msg, x.getContent())); - } - boolean online = sizeBefore < contact.getPresences().size(); - mXmppConnectionService.onContactStatusChanged.onContactStatusChanged(contact, online); - } else if (type.equals("unavailable")) { - if (from.isBareJid()) { - contact.clearPresences(); - } else { - contact.removePresence(from.getResourcepart()); - } - mXmppConnectionService.onContactStatusChanged.onContactStatusChanged(contact, false); - } else if (type.equals("subscribe")) { - if (contact.getOption(Contact.Options.PREEMPTIVE_GRANT)) { - mXmppConnectionService.sendPresencePacket(account, - mPresenceGenerator.sendPresenceUpdatesTo(contact)); - } else { - contact.setOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST); - final Conversation conversation = mXmppConnectionService.findOrCreateConversation( - account, contact.getJid().toBareJid(), false); - final String statusMessage = packet.findChildContent("status"); - if (statusMessage != null - && !statusMessage.isEmpty() - && conversation.countMessages() == 0) { - conversation.add(new Message( - conversation, - statusMessage, - Message.ENCRYPTION_NONE, - Message.STATUS_RECEIVED - )); - } - } - } - mXmppConnectionService.updateRosterUi(); - } + PgpEngine pgp = mXmppConnectionService.getPgpEngine(); + Element x = packet.findChild("x", "jabber:x:signed"); + if (pgp != null && x != null) { + Element status = packet.findChild("status"); + String msg = status != null ? status.getContent() : ""; + contact.setPgpKeyId(pgp.fetchKeyId(account, msg, x.getContent())); + } + boolean online = sizeBefore < contact.getPresences().size(); + mXmppConnectionService.onContactStatusChanged.onContactStatusChanged(contact, online); + } else if (type.equals("unavailable")) { + if (from.isBareJid()) { + contact.clearPresences(); + } else { + contact.removePresence(from.getResourcepart()); + } + mXmppConnectionService.onContactStatusChanged.onContactStatusChanged(contact, false); + } else if (type.equals("subscribe")) { + if (contact.getOption(Contact.Options.PREEMPTIVE_GRANT)) { + mXmppConnectionService.sendPresencePacket(account, + mPresenceGenerator.sendPresenceUpdatesTo(contact)); + } else { + contact.setOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST); + final Conversation conversation = mXmppConnectionService.findOrCreateConversation( + account, contact.getJid().toBareJid(), false); + final String statusMessage = packet.findChildContent("status"); + if (statusMessage != null + && !statusMessage.isEmpty() + && conversation.countMessages() == 0) { + conversation.add(new Message( + conversation, + statusMessage, + Message.ENCRYPTION_NONE, + Message.STATUS_RECEIVED + )); + } + } + } + mXmppConnectionService.updateRosterUi(); + } - @Override - public void onPresencePacketReceived(Account account, PresencePacket packet) { - if (packet.hasChild("x", "http://jabber.org/protocol/muc#user")) { - this.parseConferencePresence(packet, account); - } else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) { - this.parseConferencePresence(packet, account); - } else { - this.parseContactPresence(packet, account); - } - } + @Override + public void onPresencePacketReceived(Account account, PresencePacket packet) { + if (packet.hasChild("x", "http://jabber.org/protocol/muc#user")) { + this.parseConferencePresence(packet, account); + } else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) { + this.parseConferencePresence(packet, account); + } else { + this.parseContactPresence(packet, account); + } + } } diff --git a/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java b/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java index 4ce66edf8..993a42c21 100644 --- a/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java +++ b/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java @@ -52,1261 +52,1261 @@ import de.pixart.messenger.xmpp.jid.Jid; public class DatabaseBackend extends SQLiteOpenHelper { - private static DatabaseBackend instance = null; - - public static final String DATABASE_NAME = "history"; - public static final int DATABASE_VERSION = 31; - - private static String CREATE_CONTATCS_STATEMENT = "create table " - + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, " - + Contact.SERVERNAME + " TEXT, " + Contact.SYSTEMNAME + " TEXT," - + Contact.JID + " TEXT," + Contact.KEYS + " TEXT," - + Contact.PHOTOURI + " TEXT," + Contact.OPTIONS + " NUMBER," - + Contact.SYSTEMACCOUNT + " NUMBER, " + Contact.AVATAR + " TEXT, " - + Contact.LAST_PRESENCE + " TEXT, " + Contact.LAST_TIME + " NUMBER, " - + Contact.GROUPS + " TEXT, FOREIGN KEY(" + Contact.ACCOUNT + ") REFERENCES " - + Account.TABLENAME + "(" + Account.UUID - + ") ON DELETE CASCADE, UNIQUE(" + Contact.ACCOUNT + ", " - + Contact.JID + ") ON CONFLICT REPLACE);"; - - private static String CREATE_DISCOVERY_RESULTS_STATEMENT = "create table " - + ServiceDiscoveryResult.TABLENAME + "(" - + ServiceDiscoveryResult.HASH + " TEXT, " - + ServiceDiscoveryResult.VER + " TEXT, " - + ServiceDiscoveryResult.RESULT + " TEXT, " - + "UNIQUE(" + ServiceDiscoveryResult.HASH + ", " - + ServiceDiscoveryResult.VER + ") ON CONFLICT REPLACE);"; - - private static String CREATE_PRESENCE_TEMPLATES_STATEMENT = "CREATE TABLE " - + PresenceTemplate.TABELNAME + "(" - + PresenceTemplate.UUID + " TEXT, " - + PresenceTemplate.LAST_USED + " NUMBER," - + PresenceTemplate.MESSAGE + " TEXT," - + PresenceTemplate.STATUS + " TEXT," - + "UNIQUE("+PresenceTemplate.MESSAGE + "," +PresenceTemplate.STATUS+") ON CONFLICT REPLACE);"; - - private static String CREATE_PREKEYS_STATEMENT = "CREATE TABLE " - + SQLiteAxolotlStore.PREKEY_TABLENAME + "(" - + SQLiteAxolotlStore.ACCOUNT + " TEXT, " - + SQLiteAxolotlStore.ID + " INTEGER, " - + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" - + SQLiteAxolotlStore.ACCOUNT - + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " - + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", " - + SQLiteAxolotlStore.ID - + ") ON CONFLICT REPLACE" - + ");"; - - private static String CREATE_SIGNED_PREKEYS_STATEMENT = "CREATE TABLE " - + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME + "(" - + SQLiteAxolotlStore.ACCOUNT + " TEXT, " - + SQLiteAxolotlStore.ID + " INTEGER, " - + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" - + SQLiteAxolotlStore.ACCOUNT - + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " - + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", " - + SQLiteAxolotlStore.ID - + ") ON CONFLICT REPLACE" + - ");"; - - private static String CREATE_SESSIONS_STATEMENT = "CREATE TABLE " - + SQLiteAxolotlStore.SESSION_TABLENAME + "(" - + SQLiteAxolotlStore.ACCOUNT + " TEXT, " - + SQLiteAxolotlStore.NAME + " TEXT, " - + SQLiteAxolotlStore.DEVICE_ID + " INTEGER, " - + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" - + SQLiteAxolotlStore.ACCOUNT - + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " - + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", " - + SQLiteAxolotlStore.NAME + ", " - + SQLiteAxolotlStore.DEVICE_ID - + ") ON CONFLICT REPLACE" - + ");"; - - private static String CREATE_IDENTITIES_STATEMENT = "CREATE TABLE " - + SQLiteAxolotlStore.IDENTITIES_TABLENAME + "(" - + SQLiteAxolotlStore.ACCOUNT + " TEXT, " - + SQLiteAxolotlStore.NAME + " TEXT, " - + SQLiteAxolotlStore.OWN + " INTEGER, " - + SQLiteAxolotlStore.FINGERPRINT + " TEXT, " - + SQLiteAxolotlStore.CERTIFICATE + " BLOB, " - + SQLiteAxolotlStore.TRUST + " TEXT, " - + SQLiteAxolotlStore.ACTIVE + " NUMBER, " - + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" - + SQLiteAxolotlStore.ACCOUNT - + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " - + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", " - + SQLiteAxolotlStore.NAME + ", " - + SQLiteAxolotlStore.FINGERPRINT - + ") ON CONFLICT IGNORE" - + ");"; - - private static String START_TIMES_TABLE = "start_times"; - private static String CREATE_START_TIMES_TABLE = "create table "+START_TIMES_TABLE+" (timestamp NUMBER);"; - - private DatabaseBackend(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - } - - @Override - public void onCreate(SQLiteDatabase db) { - db.execSQL("PRAGMA foreign_keys=ON;"); - db.execSQL("create table " + Account.TABLENAME + "(" + Account.UUID+ " TEXT PRIMARY KEY," - + Account.USERNAME + " TEXT," - + Account.SERVER + " TEXT," - + Account.PASSWORD + " TEXT," - + Account.DISPLAY_NAME + " TEXT, " - + Account.STATUS + " TEXT," - + Account.STATUS_MESSAGE + " TEXT," - + Account.ROSTERVERSION + " TEXT," - + Account.OPTIONS + " NUMBER, " - + Account.AVATAR + " TEXT, " - + Account.KEYS + " TEXT, " - + Account.HOSTNAME + " TEXT, " - + Account.PORT + " NUMBER DEFAULT 5222)"); - db.execSQL("create table " + Conversation.TABLENAME + " (" - + Conversation.UUID + " TEXT PRIMARY KEY, " + Conversation.NAME - + " TEXT, " + Conversation.CONTACT + " TEXT, " - + Conversation.ACCOUNT + " TEXT, " + Conversation.CONTACTJID - + " TEXT, " + Conversation.CREATED + " NUMBER, " - + Conversation.STATUS + " NUMBER, " + Conversation.MODE - + " NUMBER, " + Conversation.ATTRIBUTES + " TEXT, FOREIGN KEY(" - + Conversation.ACCOUNT + ") REFERENCES " + Account.TABLENAME - + "(" + Account.UUID + ") ON DELETE CASCADE);"); - db.execSQL("create table " + Message.TABLENAME + "( " + Message.UUID - + " TEXT PRIMARY KEY, " + Message.CONVERSATION + " TEXT, " - + Message.TIME_SENT + " NUMBER, " + Message.COUNTERPART - + " TEXT, " + Message.TRUE_COUNTERPART + " TEXT," - + Message.BODY + " TEXT, " + Message.ENCRYPTION + " NUMBER, " - + Message.STATUS + " NUMBER," + Message.TYPE + " NUMBER, " - + Message.RELATIVE_FILE_PATH + " TEXT, " - + Message.SERVER_MSG_ID + " TEXT, " - + Message.FINGERPRINT + " TEXT, " - + Message.CARBON + " INTEGER, " - + Message.EDITED + " TEXT, " - + Message.READ + " NUMBER DEFAULT 1, " - + Message.OOB + " INTEGER, " - + Message.ERROR_MESSAGE + " TEXT," - + Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY(" - + Message.CONVERSATION + ") REFERENCES " - + Conversation.TABLENAME + "(" + Conversation.UUID - + ") ON DELETE CASCADE);"); - - db.execSQL(CREATE_CONTATCS_STATEMENT); - db.execSQL(CREATE_DISCOVERY_RESULTS_STATEMENT); - db.execSQL(CREATE_SESSIONS_STATEMENT); - db.execSQL(CREATE_PREKEYS_STATEMENT); - db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT); - db.execSQL(CREATE_IDENTITIES_STATEMENT); - db.execSQL(CREATE_PRESENCE_TEMPLATES_STATEMENT); - db.execSQL(CREATE_START_TIMES_TABLE); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - if (oldVersion < 2 && newVersion >= 2) { - db.execSQL("update " + Account.TABLENAME + " set " - + Account.OPTIONS + " = " + Account.OPTIONS + " | 8"); - } - if (oldVersion < 3 && newVersion >= 3) { - db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " - + Message.TYPE + " NUMBER"); - } - if (oldVersion < 5 && newVersion >= 5) { - db.execSQL("DROP TABLE " + Contact.TABLENAME); - db.execSQL(CREATE_CONTATCS_STATEMENT); - db.execSQL("UPDATE " + Account.TABLENAME + " SET " - + Account.ROSTERVERSION + " = NULL"); - } - if (oldVersion < 6 && newVersion >= 6) { - db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " - + Message.TRUE_COUNTERPART + " TEXT"); - } - if (oldVersion < 7 && newVersion >= 7) { - db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " - + Message.REMOTE_MSG_ID + " TEXT"); - db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " - + Contact.AVATAR + " TEXT"); - db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " - + Account.AVATAR + " TEXT"); - } - if (oldVersion < 8 && newVersion >= 8) { - db.execSQL("ALTER TABLE " + Conversation.TABLENAME + " ADD COLUMN " - + Conversation.ATTRIBUTES + " TEXT"); - } - if (oldVersion < 9 && newVersion >= 9) { - db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " - + Contact.LAST_TIME + " NUMBER"); - db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " - + Contact.LAST_PRESENCE + " TEXT"); - } - if (oldVersion < 10 && newVersion >= 10) { - db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " - + Message.RELATIVE_FILE_PATH + " TEXT"); - } - if (oldVersion < 11 && newVersion >= 11) { - db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " - + Contact.GROUPS + " TEXT"); - db.execSQL("delete from " + Contact.TABLENAME); - db.execSQL("update " + Account.TABLENAME + " set " + Account.ROSTERVERSION + " = NULL"); - } - if (oldVersion < 12 && newVersion >= 12) { - db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " - + Message.SERVER_MSG_ID + " TEXT"); - } - if (oldVersion < 13 && newVersion >= 13) { - db.execSQL("delete from " + Contact.TABLENAME); - db.execSQL("update " + Account.TABLENAME + " set " + Account.ROSTERVERSION + " = NULL"); - } - if (oldVersion < 14 && newVersion >= 14) { - canonicalizeJids(db); - } - if (oldVersion < 15 && newVersion >= 15) { - recreateAxolotlDb(db); - db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " - + Message.FINGERPRINT + " TEXT"); - } else if (oldVersion < 22 && newVersion >= 22) { - db.execSQL("ALTER TABLE " + SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN " + SQLiteAxolotlStore.CERTIFICATE); - } - if (oldVersion < 16 && newVersion >= 16) { - db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " - + Message.CARBON + " INTEGER"); - } - if (oldVersion < 19 && newVersion >= 19) { - db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.DISPLAY_NAME + " TEXT"); - } - if (oldVersion < 20 && newVersion >= 20) { - db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.HOSTNAME + " TEXT"); - db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.PORT + " NUMBER DEFAULT 5222"); - } - if (oldVersion < 26 && newVersion >= 26) { - db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.STATUS + " TEXT"); - db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.STATUS_MESSAGE + " TEXT"); - } - /* Any migrations that alter the Account table need to happen BEFORE this migration, as it + private static DatabaseBackend instance = null; + + public static final String DATABASE_NAME = "history"; + public static final int DATABASE_VERSION = 31; + + private static String CREATE_CONTATCS_STATEMENT = "create table " + + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, " + + Contact.SERVERNAME + " TEXT, " + Contact.SYSTEMNAME + " TEXT," + + Contact.JID + " TEXT," + Contact.KEYS + " TEXT," + + Contact.PHOTOURI + " TEXT," + Contact.OPTIONS + " NUMBER," + + Contact.SYSTEMACCOUNT + " NUMBER, " + Contact.AVATAR + " TEXT, " + + Contact.LAST_PRESENCE + " TEXT, " + Contact.LAST_TIME + " NUMBER, " + + Contact.GROUPS + " TEXT, FOREIGN KEY(" + Contact.ACCOUNT + ") REFERENCES " + + Account.TABLENAME + "(" + Account.UUID + + ") ON DELETE CASCADE, UNIQUE(" + Contact.ACCOUNT + ", " + + Contact.JID + ") ON CONFLICT REPLACE);"; + + private static String CREATE_DISCOVERY_RESULTS_STATEMENT = "create table " + + ServiceDiscoveryResult.TABLENAME + "(" + + ServiceDiscoveryResult.HASH + " TEXT, " + + ServiceDiscoveryResult.VER + " TEXT, " + + ServiceDiscoveryResult.RESULT + " TEXT, " + + "UNIQUE(" + ServiceDiscoveryResult.HASH + ", " + + ServiceDiscoveryResult.VER + ") ON CONFLICT REPLACE);"; + + private static String CREATE_PRESENCE_TEMPLATES_STATEMENT = "CREATE TABLE " + + PresenceTemplate.TABELNAME + "(" + + PresenceTemplate.UUID + " TEXT, " + + PresenceTemplate.LAST_USED + " NUMBER," + + PresenceTemplate.MESSAGE + " TEXT," + + PresenceTemplate.STATUS + " TEXT," + + "UNIQUE(" + PresenceTemplate.MESSAGE + "," + PresenceTemplate.STATUS + ") ON CONFLICT REPLACE);"; + + private static String CREATE_PREKEYS_STATEMENT = "CREATE TABLE " + + SQLiteAxolotlStore.PREKEY_TABLENAME + "(" + + SQLiteAxolotlStore.ACCOUNT + " TEXT, " + + SQLiteAxolotlStore.ID + " INTEGER, " + + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" + + SQLiteAxolotlStore.ACCOUNT + + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " + + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", " + + SQLiteAxolotlStore.ID + + ") ON CONFLICT REPLACE" + + ");"; + + private static String CREATE_SIGNED_PREKEYS_STATEMENT = "CREATE TABLE " + + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME + "(" + + SQLiteAxolotlStore.ACCOUNT + " TEXT, " + + SQLiteAxolotlStore.ID + " INTEGER, " + + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" + + SQLiteAxolotlStore.ACCOUNT + + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " + + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", " + + SQLiteAxolotlStore.ID + + ") ON CONFLICT REPLACE" + + ");"; + + private static String CREATE_SESSIONS_STATEMENT = "CREATE TABLE " + + SQLiteAxolotlStore.SESSION_TABLENAME + "(" + + SQLiteAxolotlStore.ACCOUNT + " TEXT, " + + SQLiteAxolotlStore.NAME + " TEXT, " + + SQLiteAxolotlStore.DEVICE_ID + " INTEGER, " + + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" + + SQLiteAxolotlStore.ACCOUNT + + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " + + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", " + + SQLiteAxolotlStore.NAME + ", " + + SQLiteAxolotlStore.DEVICE_ID + + ") ON CONFLICT REPLACE" + + ");"; + + private static String CREATE_IDENTITIES_STATEMENT = "CREATE TABLE " + + SQLiteAxolotlStore.IDENTITIES_TABLENAME + "(" + + SQLiteAxolotlStore.ACCOUNT + " TEXT, " + + SQLiteAxolotlStore.NAME + " TEXT, " + + SQLiteAxolotlStore.OWN + " INTEGER, " + + SQLiteAxolotlStore.FINGERPRINT + " TEXT, " + + SQLiteAxolotlStore.CERTIFICATE + " BLOB, " + + SQLiteAxolotlStore.TRUST + " TEXT, " + + SQLiteAxolotlStore.ACTIVE + " NUMBER, " + + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" + + SQLiteAxolotlStore.ACCOUNT + + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " + + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", " + + SQLiteAxolotlStore.NAME + ", " + + SQLiteAxolotlStore.FINGERPRINT + + ") ON CONFLICT IGNORE" + + ");"; + + private static String START_TIMES_TABLE = "start_times"; + private static String CREATE_START_TIMES_TABLE = "create table " + START_TIMES_TABLE + " (timestamp NUMBER);"; + + private DatabaseBackend(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("PRAGMA foreign_keys=ON;"); + db.execSQL("create table " + Account.TABLENAME + "(" + Account.UUID + " TEXT PRIMARY KEY," + + Account.USERNAME + " TEXT," + + Account.SERVER + " TEXT," + + Account.PASSWORD + " TEXT," + + Account.DISPLAY_NAME + " TEXT, " + + Account.STATUS + " TEXT," + + Account.STATUS_MESSAGE + " TEXT," + + Account.ROSTERVERSION + " TEXT," + + Account.OPTIONS + " NUMBER, " + + Account.AVATAR + " TEXT, " + + Account.KEYS + " TEXT, " + + Account.HOSTNAME + " TEXT, " + + Account.PORT + " NUMBER DEFAULT 5222)"); + db.execSQL("create table " + Conversation.TABLENAME + " (" + + Conversation.UUID + " TEXT PRIMARY KEY, " + Conversation.NAME + + " TEXT, " + Conversation.CONTACT + " TEXT, " + + Conversation.ACCOUNT + " TEXT, " + Conversation.CONTACTJID + + " TEXT, " + Conversation.CREATED + " NUMBER, " + + Conversation.STATUS + " NUMBER, " + Conversation.MODE + + " NUMBER, " + Conversation.ATTRIBUTES + " TEXT, FOREIGN KEY(" + + Conversation.ACCOUNT + ") REFERENCES " + Account.TABLENAME + + "(" + Account.UUID + ") ON DELETE CASCADE);"); + db.execSQL("create table " + Message.TABLENAME + "( " + Message.UUID + + " TEXT PRIMARY KEY, " + Message.CONVERSATION + " TEXT, " + + Message.TIME_SENT + " NUMBER, " + Message.COUNTERPART + + " TEXT, " + Message.TRUE_COUNTERPART + " TEXT," + + Message.BODY + " TEXT, " + Message.ENCRYPTION + " NUMBER, " + + Message.STATUS + " NUMBER," + Message.TYPE + " NUMBER, " + + Message.RELATIVE_FILE_PATH + " TEXT, " + + Message.SERVER_MSG_ID + " TEXT, " + + Message.FINGERPRINT + " TEXT, " + + Message.CARBON + " INTEGER, " + + Message.EDITED + " TEXT, " + + Message.READ + " NUMBER DEFAULT 1, " + + Message.OOB + " INTEGER, " + + Message.ERROR_MESSAGE + " TEXT," + + Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY(" + + Message.CONVERSATION + ") REFERENCES " + + Conversation.TABLENAME + "(" + Conversation.UUID + + ") ON DELETE CASCADE);"); + + db.execSQL(CREATE_CONTATCS_STATEMENT); + db.execSQL(CREATE_DISCOVERY_RESULTS_STATEMENT); + db.execSQL(CREATE_SESSIONS_STATEMENT); + db.execSQL(CREATE_PREKEYS_STATEMENT); + db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT); + db.execSQL(CREATE_IDENTITIES_STATEMENT); + db.execSQL(CREATE_PRESENCE_TEMPLATES_STATEMENT); + db.execSQL(CREATE_START_TIMES_TABLE); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + if (oldVersion < 2 && newVersion >= 2) { + db.execSQL("update " + Account.TABLENAME + " set " + + Account.OPTIONS + " = " + Account.OPTIONS + " | 8"); + } + if (oldVersion < 3 && newVersion >= 3) { + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + + Message.TYPE + " NUMBER"); + } + if (oldVersion < 5 && newVersion >= 5) { + db.execSQL("DROP TABLE " + Contact.TABLENAME); + db.execSQL(CREATE_CONTATCS_STATEMENT); + db.execSQL("UPDATE " + Account.TABLENAME + " SET " + + Account.ROSTERVERSION + " = NULL"); + } + if (oldVersion < 6 && newVersion >= 6) { + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + + Message.TRUE_COUNTERPART + " TEXT"); + } + if (oldVersion < 7 && newVersion >= 7) { + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + + Message.REMOTE_MSG_ID + " TEXT"); + db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " + + Contact.AVATAR + " TEXT"); + db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + + Account.AVATAR + " TEXT"); + } + if (oldVersion < 8 && newVersion >= 8) { + db.execSQL("ALTER TABLE " + Conversation.TABLENAME + " ADD COLUMN " + + Conversation.ATTRIBUTES + " TEXT"); + } + if (oldVersion < 9 && newVersion >= 9) { + db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " + + Contact.LAST_TIME + " NUMBER"); + db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " + + Contact.LAST_PRESENCE + " TEXT"); + } + if (oldVersion < 10 && newVersion >= 10) { + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + + Message.RELATIVE_FILE_PATH + " TEXT"); + } + if (oldVersion < 11 && newVersion >= 11) { + db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " + + Contact.GROUPS + " TEXT"); + db.execSQL("delete from " + Contact.TABLENAME); + db.execSQL("update " + Account.TABLENAME + " set " + Account.ROSTERVERSION + " = NULL"); + } + if (oldVersion < 12 && newVersion >= 12) { + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + + Message.SERVER_MSG_ID + " TEXT"); + } + if (oldVersion < 13 && newVersion >= 13) { + db.execSQL("delete from " + Contact.TABLENAME); + db.execSQL("update " + Account.TABLENAME + " set " + Account.ROSTERVERSION + " = NULL"); + } + if (oldVersion < 14 && newVersion >= 14) { + canonicalizeJids(db); + } + if (oldVersion < 15 && newVersion >= 15) { + recreateAxolotlDb(db); + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + + Message.FINGERPRINT + " TEXT"); + } else if (oldVersion < 22 && newVersion >= 22) { + db.execSQL("ALTER TABLE " + SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN " + SQLiteAxolotlStore.CERTIFICATE); + } + if (oldVersion < 16 && newVersion >= 16) { + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + + Message.CARBON + " INTEGER"); + } + if (oldVersion < 19 && newVersion >= 19) { + db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.DISPLAY_NAME + " TEXT"); + } + if (oldVersion < 20 && newVersion >= 20) { + db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.HOSTNAME + " TEXT"); + db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.PORT + " NUMBER DEFAULT 5222"); + } + if (oldVersion < 26 && newVersion >= 26) { + db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.STATUS + " TEXT"); + db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.STATUS_MESSAGE + " TEXT"); + } + /* Any migrations that alter the Account table need to happen BEFORE this migration, as it * depends on account de-serialization. */ - if (oldVersion < 17 && newVersion >= 17) { - List accounts = getAccounts(db); - for (Account account : accounts) { - String ownDeviceIdString = account.getKey(SQLiteAxolotlStore.JSONKEY_REGISTRATION_ID); - if (ownDeviceIdString == null) { - continue; - } - int ownDeviceId = Integer.valueOf(ownDeviceIdString); - AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), ownDeviceId); - deleteSession(db, account, ownAddress); - IdentityKeyPair identityKeyPair = loadOwnIdentityKeyPair(db, account); - if (identityKeyPair != null) { - String[] selectionArgs = { - account.getUuid(), - identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", "") - }; - ContentValues values = new ContentValues(); - values.put(SQLiteAxolotlStore.TRUSTED, 2); - db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values, - SQLiteAxolotlStore.ACCOUNT + " = ? AND " - + SQLiteAxolotlStore.FINGERPRINT + " = ? ", - selectionArgs); - } else { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not load own identity key pair"); - } - } - } - if (oldVersion < 18 && newVersion >= 18) { - db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.READ + " NUMBER DEFAULT 1"); - } - - if (oldVersion < 21 && newVersion >= 21) { - List accounts = getAccounts(db); - for (Account account : accounts) { - account.unsetPgpSignature(); - db.update(Account.TABLENAME, account.getContentValues(), Account.UUID - + "=?", new String[]{account.getUuid()}); - } - } - - if (oldVersion < 23 && newVersion >= 23) { - db.execSQL(CREATE_DISCOVERY_RESULTS_STATEMENT); - } - - if (oldVersion < 24 && newVersion >= 24) { - db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.EDITED + " TEXT"); - } - - if (oldVersion < 25 && newVersion >= 25) { - db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.OOB + " INTEGER"); - } - - if (oldVersion < 26 && newVersion >= 26) { - db.execSQL(CREATE_PRESENCE_TEMPLATES_STATEMENT); - } - - if (oldVersion < 27 && newVersion >= 27) { - db.execSQL("DELETE FROM "+ServiceDiscoveryResult.TABLENAME); - } - - if (oldVersion < 28 && newVersion >= 28) { - canonicalizeJids(db); - } - - if (oldVersion < 29 && newVersion >= 29) { - db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.ERROR_MESSAGE + " TEXT"); - } - - if (oldVersion < 30 && newVersion >= 30) { - db.execSQL(CREATE_START_TIMES_TABLE); - } - if (oldVersion < 31 && newVersion >= 31) { - db.execSQL("ALTER TABLE "+ SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN "+SQLiteAxolotlStore.TRUST + " TEXT"); - db.execSQL("ALTER TABLE "+ SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN "+SQLiteAxolotlStore.ACTIVE + " NUMBER"); - HashMap migration = new HashMap<>(); - migration.put(0,createFingerprintStatusContentValues(FingerprintStatus.Trust.UNDECIDED,true)); - migration.put(1,createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED, true)); - migration.put(2,createFingerprintStatusContentValues(FingerprintStatus.Trust.UNTRUSTED, true)); - migration.put(3,createFingerprintStatusContentValues(FingerprintStatus.Trust.COMPROMISED, false)); - migration.put(4,createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED, false)); - migration.put(5,createFingerprintStatusContentValues(FingerprintStatus.Trust.UNDECIDED, false)); - migration.put(6,createFingerprintStatusContentValues(FingerprintStatus.Trust.UNTRUSTED, false)); - migration.put(7,createFingerprintStatusContentValues(FingerprintStatus.Trust.VERIFIED_X509, true)); - migration.put(8,createFingerprintStatusContentValues(FingerprintStatus.Trust.VERIFIED_X509, false)); - for(Map.Entry entry : migration.entrySet()) { - String whereClause = SQLiteAxolotlStore.TRUSTED+"=?"; - String[] where = {String.valueOf(entry.getKey())}; - db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME,entry.getValue(),whereClause,where); - } - } - } - - private static ContentValues createFingerprintStatusContentValues(FingerprintStatus.Trust trust, boolean active) { - ContentValues values = new ContentValues(); - values.put(SQLiteAxolotlStore.TRUST,trust.toString()); - values.put(SQLiteAxolotlStore.ACTIVE,active ? 1 : 0); - return values; - } - - private void canonicalizeJids(SQLiteDatabase db) { - // migrate db to new, canonicalized JID domainpart representation - - // Conversation table - Cursor cursor = db.rawQuery("select * from " + Conversation.TABLENAME, new String[0]); - while (cursor.moveToNext()) { - String newJid; - try { - newJid = Jid.fromString( - cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID)) - ).toPreppedString(); - } catch (InvalidJidException ignored) { - Log.e(Config.LOGTAG, "Failed to migrate Conversation CONTACTJID " - + cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID)) - + ": " + ignored + ". Skipping..."); - continue; - } - - String updateArgs[] = { - newJid, - cursor.getString(cursor.getColumnIndex(Conversation.UUID)), - }; - db.execSQL("update " + Conversation.TABLENAME - + " set " + Conversation.CONTACTJID + " = ? " - + " where " + Conversation.UUID + " = ?", updateArgs); - } - cursor.close(); - - // Contact table - cursor = db.rawQuery("select * from " + Contact.TABLENAME, new String[0]); - while (cursor.moveToNext()) { - String newJid; - try { - newJid = Jid.fromString( - cursor.getString(cursor.getColumnIndex(Contact.JID)) - ).toPreppedString(); - } catch (InvalidJidException ignored) { - Log.e(Config.LOGTAG, "Failed to migrate Contact JID " - + cursor.getString(cursor.getColumnIndex(Contact.JID)) - + ": " + ignored + ". Skipping..."); - continue; - } - - String updateArgs[] = { - newJid, - cursor.getString(cursor.getColumnIndex(Contact.ACCOUNT)), - cursor.getString(cursor.getColumnIndex(Contact.JID)), - }; - db.execSQL("update " + Contact.TABLENAME - + " set " + Contact.JID + " = ? " - + " where " + Contact.ACCOUNT + " = ? " - + " AND " + Contact.JID + " = ?", updateArgs); - } - cursor.close(); - - // Account table - cursor = db.rawQuery("select * from " + Account.TABLENAME, new String[0]); - while (cursor.moveToNext()) { - String newServer; - try { - newServer = Jid.fromParts( - cursor.getString(cursor.getColumnIndex(Account.USERNAME)), - cursor.getString(cursor.getColumnIndex(Account.SERVER)), - "mobile" - ).getDomainpart(); - } catch (InvalidJidException ignored) { - Log.e(Config.LOGTAG, "Failed to migrate Account SERVER " - + cursor.getString(cursor.getColumnIndex(Account.SERVER)) - + ": " + ignored + ". Skipping..."); - continue; - } - - String updateArgs[] = { - newServer, - cursor.getString(cursor.getColumnIndex(Account.UUID)), - }; - db.execSQL("update " + Account.TABLENAME - + " set " + Account.SERVER + " = ? " - + " where " + Account.UUID + " = ?", updateArgs); - } - cursor.close(); - } - - public static synchronized DatabaseBackend getInstance(Context context) { - if (instance == null) { - instance = new DatabaseBackend(context); - } - return instance; - } - - public void createConversation(Conversation conversation) { - SQLiteDatabase db = this.getWritableDatabase(); - db.insert(Conversation.TABLENAME, null, conversation.getContentValues()); - } - - public void createMessage(Message message) { - SQLiteDatabase db = this.getWritableDatabase(); - db.insert(Message.TABLENAME, null, message.getContentValues()); - } - - public void createAccount(Account account) { - SQLiteDatabase db = this.getWritableDatabase(); - db.insert(Account.TABLENAME, null, account.getContentValues()); - } - - public void insertDiscoveryResult(ServiceDiscoveryResult result) { - SQLiteDatabase db = this.getWritableDatabase(); - db.insert(ServiceDiscoveryResult.TABLENAME, null, result.getContentValues()); - } - - public ServiceDiscoveryResult findDiscoveryResult(final String hash, final String ver) { - SQLiteDatabase db = this.getReadableDatabase(); - String[] selectionArgs = {hash, ver}; - Cursor cursor = db.query(ServiceDiscoveryResult.TABLENAME, null, - ServiceDiscoveryResult.HASH + "=? AND " + ServiceDiscoveryResult.VER + "=?", - selectionArgs, null, null, null); - if (cursor.getCount() == 0) { - cursor.close(); - return null; - } - cursor.moveToFirst(); - - ServiceDiscoveryResult result = null; - try { - result = new ServiceDiscoveryResult(cursor); - } catch (JSONException e) { /* result is still null */ } - - cursor.close(); - return result; - } - - public void insertPresenceTemplate(PresenceTemplate template) { - SQLiteDatabase db = this.getWritableDatabase(); - db.insert(PresenceTemplate.TABELNAME, null, template.getContentValues()); - } - - public List getPresenceTemplates() { - ArrayList templates = new ArrayList<>(); - SQLiteDatabase db = this.getReadableDatabase(); - Cursor cursor = db.query(PresenceTemplate.TABELNAME,null,null,null,null,null,PresenceTemplate.LAST_USED+" desc"); - while (cursor.moveToNext()) { - templates.add(PresenceTemplate.fromCursor(cursor)); - } - cursor.close(); - return templates; - } - - public void deletePresenceTemplate(PresenceTemplate template) { - Log.d(Config.LOGTAG,"deleting presence template with uuid "+template.getUuid()); - SQLiteDatabase db = this.getWritableDatabase(); - String where = PresenceTemplate.UUID+"=?"; - String[] whereArgs = {template.getUuid()}; - db.delete(PresenceTemplate.TABELNAME,where,whereArgs); - } - - public CopyOnWriteArrayList getConversations(int status) { - CopyOnWriteArrayList list = new CopyOnWriteArrayList<>(); - SQLiteDatabase db = this.getReadableDatabase(); - String[] selectionArgs = {Integer.toString(status)}; - Cursor cursor = db.rawQuery("select * from " + Conversation.TABLENAME - + " where " + Conversation.STATUS + " = ? order by " - + Conversation.CREATED + " desc", selectionArgs); - while (cursor.moveToNext()) { - list.add(Conversation.fromCursor(cursor)); - } - cursor.close(); - return list; - } - - public ArrayList getMessages(Conversation conversations, int limit) { - return getMessages(conversations, limit, -1); - } - - public ArrayList getMessages(Conversation conversation, int limit, - long timestamp) { - ArrayList list = new ArrayList<>(); - SQLiteDatabase db = this.getReadableDatabase(); - Cursor cursor; - if (timestamp == -1) { - String[] selectionArgs = {conversation.getUuid()}; - cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION - + "=?", selectionArgs, null, null, Message.TIME_SENT - + " DESC", String.valueOf(limit)); - } else { - String[] selectionArgs = {conversation.getUuid(), - Long.toString(timestamp)}; - cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION - + "=? and " + Message.TIME_SENT + " 0) { - cursor.moveToLast(); - do { - Message message = Message.fromCursor(cursor); - message.setConversation(conversation); - list.add(message); - } while (cursor.moveToPrevious()); - } - cursor.close(); - return list; - } - - public Iterable getMessagesIterable(final Conversation conversation) { - return new Iterable() { - @Override - public Iterator iterator() { - class MessageIterator implements Iterator { - SQLiteDatabase db = getReadableDatabase(); - String[] selectionArgs = {conversation.getUuid()}; - Cursor cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION - + "=?", selectionArgs, null, null, Message.TIME_SENT - + " ASC", null); - - public MessageIterator() { - cursor.moveToFirst(); - } - - @Override - public boolean hasNext() { - return !cursor.isAfterLast(); - } - - @Override - public Message next() { - Message message = Message.fromCursor(cursor); - cursor.moveToNext(); - return message; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - } - return new MessageIterator(); - } - }; - } - - public Conversation findConversation(final Account account, final Jid contactJid) { - SQLiteDatabase db = this.getReadableDatabase(); - String[] selectionArgs = {account.getUuid(), - contactJid.toBareJid().toPreppedString() + "/%", - contactJid.toBareJid().toPreppedString() - }; - Cursor cursor = db.query(Conversation.TABLENAME, null, - Conversation.ACCOUNT + "=? AND (" + Conversation.CONTACTJID - + " like ? OR " + Conversation.CONTACTJID + "=?)", selectionArgs, null, null, null); - if (cursor.getCount() == 0) { - cursor.close(); - return null; - } - cursor.moveToFirst(); - Conversation conversation = Conversation.fromCursor(cursor); - cursor.close(); - return conversation; - } - - public void updateConversation(final Conversation conversation) { - final SQLiteDatabase db = this.getWritableDatabase(); - final String[] args = {conversation.getUuid()}; - db.update(Conversation.TABLENAME, conversation.getContentValues(), - Conversation.UUID + "=?", args); - } - - public List getAccounts() { - SQLiteDatabase db = this.getReadableDatabase(); - return getAccounts(db); - } - - private List getAccounts(SQLiteDatabase db) { - List list = new ArrayList<>(); - Cursor cursor = db.query(Account.TABLENAME, null, null, null, null, - null, null); - while (cursor.moveToNext()) { - list.add(Account.fromCursor(cursor)); - } - cursor.close(); - return list; - } - - public boolean updateAccount(Account account) { - SQLiteDatabase db = this.getWritableDatabase(); - String[] args = {account.getUuid()}; - final int rows = db.update(Account.TABLENAME, account.getContentValues(), Account.UUID + "=?", args); - return rows == 1; - } - - public boolean deleteAccount(Account account) { - SQLiteDatabase db = this.getWritableDatabase(); - String[] args = {account.getUuid()}; - final int rows = db.delete(Account.TABLENAME, Account.UUID + "=?", args); - return rows == 1; - } - - public boolean hasEnabledAccounts() { - SQLiteDatabase db = this.getReadableDatabase(); - Cursor cursor = db.rawQuery("select count(" + Account.UUID + ") from " - + Account.TABLENAME + " where not options & (1 <<1)", null); - try { - cursor.moveToFirst(); - int count = cursor.getInt(0); - return (count > 0); - } catch (SQLiteCantOpenDatabaseException e) { - return true; // better safe than sorry - } catch (RuntimeException e) { - return true; // better safe than sorry - } finally { - if (cursor != null) { - cursor.close(); - } - } - } - - @Override - public SQLiteDatabase getWritableDatabase() { - SQLiteDatabase db = super.getWritableDatabase(); - db.execSQL("PRAGMA foreign_keys=ON;"); - return db; - } - - public void updateMessage(Message message) { - SQLiteDatabase db = this.getWritableDatabase(); - String[] args = {message.getUuid()}; - db.update(Message.TABLENAME, message.getContentValues(), Message.UUID - + "=?", args); - } - - public void updateMessage(Message message, String uuid) { - SQLiteDatabase db = this.getWritableDatabase(); - String[] args = {uuid}; - db.update(Message.TABLENAME, message.getContentValues(), Message.UUID - + "=?", args); - } - - public void readRoster(Roster roster) { - SQLiteDatabase db = this.getReadableDatabase(); - Cursor cursor; - String args[] = {roster.getAccount().getUuid()}; - cursor = db.query(Contact.TABLENAME, null, Contact.ACCOUNT + "=?", args, null, null, null); - while (cursor.moveToNext()) { - roster.initContact(Contact.fromCursor(cursor)); - } - cursor.close(); - } - - public void writeRoster(final Roster roster) { - final Account account = roster.getAccount(); - final SQLiteDatabase db = this.getWritableDatabase(); - db.beginTransaction(); - for (Contact contact : roster.getContacts()) { - if (contact.getOption(Contact.Options.IN_ROSTER)) { - db.insert(Contact.TABLENAME, null, contact.getContentValues()); - } else { - String where = Contact.ACCOUNT + "=? AND " + Contact.JID + "=?"; - String[] whereArgs = {account.getUuid(), contact.getJid().toPreppedString()}; - db.delete(Contact.TABLENAME, where, whereArgs); - } - } - db.setTransactionSuccessful(); - db.endTransaction(); - account.setRosterVersion(roster.getVersion()); - updateAccount(account); - } - - public void deleteMessagesInConversation(Conversation conversation) { - SQLiteDatabase db = this.getWritableDatabase(); - String[] args = {conversation.getUuid()}; - db.delete(Message.TABLENAME, Message.CONVERSATION + "=?", args); - } - - public Pair getLastMessageReceived(Account account) { - Cursor cursor = null; - try { - SQLiteDatabase db = this.getReadableDatabase(); - String sql = "select messages.timeSent,messages.serverMsgId from accounts join conversations on accounts.uuid=conversations.accountUuid join messages on conversations.uuid=messages.conversationUuid where accounts.uuid=? and (messages.status=0 or messages.carbon=1 or messages.serverMsgId not null) order by messages.timesent desc limit 1"; - String[] args = {account.getUuid()}; - cursor = db.rawQuery(sql, args); - if (cursor.getCount() == 0) { - return null; - } else { - cursor.moveToFirst(); - return new Pair<>(cursor.getLong(0), cursor.getString(1)); - } - } catch (Exception e) { - return null; - } finally { - if (cursor != null) { - cursor.close(); - } - } - } - - public Pair getLastClearDate(Account account) { - SQLiteDatabase db = this.getReadableDatabase(); - String[] columns = {Conversation.ATTRIBUTES}; - String selection = Conversation.ACCOUNT + "=?"; - String[] args = {account.getUuid()}; - Cursor cursor = db.query(Conversation.TABLENAME, columns, selection, args, null, null, null); - long maxClearDate = 0; - while (cursor.moveToNext()) { - try { - final JSONObject jsonObject = new JSONObject(cursor.getString(0)); - maxClearDate = Math.max(maxClearDate, jsonObject.getLong(Conversation.ATTRIBUTE_LAST_CLEAR_HISTORY)); - } catch (Exception e) { - //ignored - } - } - cursor.close(); - return new Pair<>(maxClearDate, null); - } - - private Cursor getCursorForSession(Account account, AxolotlAddress contact) { - final SQLiteDatabase db = this.getReadableDatabase(); - String[] selectionArgs = {account.getUuid(), - contact.getName(), - Integer.toString(contact.getDeviceId())}; - return db.query(SQLiteAxolotlStore.SESSION_TABLENAME, - null, - SQLiteAxolotlStore.ACCOUNT + " = ? AND " - + SQLiteAxolotlStore.NAME + " = ? AND " - + SQLiteAxolotlStore.DEVICE_ID + " = ? ", - selectionArgs, - null, null, null); - } - - public SessionRecord loadSession(Account account, AxolotlAddress contact) { - SessionRecord session = null; - Cursor cursor = getCursorForSession(account, contact); - if (cursor.getCount() != 0) { - cursor.moveToFirst(); - try { - session = new SessionRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT)); - } catch (IOException e) { - cursor.close(); - throw new AssertionError(e); - } - } - cursor.close(); - return session; - } - - public List getSubDeviceSessions(Account account, AxolotlAddress contact) { - final SQLiteDatabase db = this.getReadableDatabase(); - return getSubDeviceSessions(db, account, contact); - } - - private List getSubDeviceSessions(SQLiteDatabase db, Account account, AxolotlAddress contact) { - List devices = new ArrayList<>(); - String[] columns = {SQLiteAxolotlStore.DEVICE_ID}; - String[] selectionArgs = {account.getUuid(), - contact.getName()}; - Cursor cursor = db.query(SQLiteAxolotlStore.SESSION_TABLENAME, - columns, - SQLiteAxolotlStore.ACCOUNT + " = ? AND " - + SQLiteAxolotlStore.NAME + " = ?", - selectionArgs, - null, null, null); - - while (cursor.moveToNext()) { - devices.add(cursor.getInt( - cursor.getColumnIndex(SQLiteAxolotlStore.DEVICE_ID))); - } - - cursor.close(); - return devices; - } - - public boolean containsSession(Account account, AxolotlAddress contact) { - Cursor cursor = getCursorForSession(account, contact); - int count = cursor.getCount(); - cursor.close(); - return count != 0; - } - - public void storeSession(Account account, AxolotlAddress contact, SessionRecord session) { - SQLiteDatabase db = this.getWritableDatabase(); - ContentValues values = new ContentValues(); - values.put(SQLiteAxolotlStore.NAME, contact.getName()); - values.put(SQLiteAxolotlStore.DEVICE_ID, contact.getDeviceId()); - values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(session.serialize(), Base64.DEFAULT)); - values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); - db.insert(SQLiteAxolotlStore.SESSION_TABLENAME, null, values); - } - - public void deleteSession(Account account, AxolotlAddress contact) { - SQLiteDatabase db = this.getWritableDatabase(); - deleteSession(db, account, contact); - } - - private void deleteSession(SQLiteDatabase db, Account account, AxolotlAddress contact) { - String[] args = {account.getUuid(), - contact.getName(), - Integer.toString(contact.getDeviceId())}; - db.delete(SQLiteAxolotlStore.SESSION_TABLENAME, - SQLiteAxolotlStore.ACCOUNT + " = ? AND " - + SQLiteAxolotlStore.NAME + " = ? AND " - + SQLiteAxolotlStore.DEVICE_ID + " = ? ", - args); - } - - public void deleteAllSessions(Account account, AxolotlAddress contact) { - SQLiteDatabase db = this.getWritableDatabase(); - String[] args = {account.getUuid(), contact.getName()}; - db.delete(SQLiteAxolotlStore.SESSION_TABLENAME, - SQLiteAxolotlStore.ACCOUNT + "=? AND " - + SQLiteAxolotlStore.NAME + " = ?", - args); - } - - private Cursor getCursorForPreKey(Account account, int preKeyId) { - SQLiteDatabase db = this.getReadableDatabase(); - String[] columns = {SQLiteAxolotlStore.KEY}; - String[] selectionArgs = {account.getUuid(), Integer.toString(preKeyId)}; - Cursor cursor = db.query(SQLiteAxolotlStore.PREKEY_TABLENAME, - columns, - SQLiteAxolotlStore.ACCOUNT + "=? AND " - + SQLiteAxolotlStore.ID + "=?", - selectionArgs, - null, null, null); - - return cursor; - } - - public PreKeyRecord loadPreKey(Account account, int preKeyId) { - PreKeyRecord record = null; - Cursor cursor = getCursorForPreKey(account, preKeyId); - if (cursor.getCount() != 0) { - cursor.moveToFirst(); - try { - record = new PreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT)); - } catch (IOException e) { - throw new AssertionError(e); - } - } - cursor.close(); - return record; - } - - public boolean containsPreKey(Account account, int preKeyId) { - Cursor cursor = getCursorForPreKey(account, preKeyId); - int count = cursor.getCount(); - cursor.close(); - return count != 0; - } - - public void storePreKey(Account account, PreKeyRecord record) { - SQLiteDatabase db = this.getWritableDatabase(); - ContentValues values = new ContentValues(); - values.put(SQLiteAxolotlStore.ID, record.getId()); - values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(), Base64.DEFAULT)); - values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); - db.insert(SQLiteAxolotlStore.PREKEY_TABLENAME, null, values); - } - - public void deletePreKey(Account account, int preKeyId) { - SQLiteDatabase db = this.getWritableDatabase(); - String[] args = {account.getUuid(), Integer.toString(preKeyId)}; - db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME, - SQLiteAxolotlStore.ACCOUNT + "=? AND " - + SQLiteAxolotlStore.ID + "=?", - args); - } - - private Cursor getCursorForSignedPreKey(Account account, int signedPreKeyId) { - SQLiteDatabase db = this.getReadableDatabase(); - String[] columns = {SQLiteAxolotlStore.KEY}; - String[] selectionArgs = {account.getUuid(), Integer.toString(signedPreKeyId)}; - Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, - columns, - SQLiteAxolotlStore.ACCOUNT + "=? AND " + SQLiteAxolotlStore.ID + "=?", - selectionArgs, - null, null, null); - - return cursor; - } - - public SignedPreKeyRecord loadSignedPreKey(Account account, int signedPreKeyId) { - SignedPreKeyRecord record = null; - Cursor cursor = getCursorForSignedPreKey(account, signedPreKeyId); - if (cursor.getCount() != 0) { - cursor.moveToFirst(); - try { - record = new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT)); - } catch (IOException e) { - throw new AssertionError(e); - } - } - cursor.close(); - return record; - } - - public List loadSignedPreKeys(Account account) { - List prekeys = new ArrayList<>(); - SQLiteDatabase db = this.getReadableDatabase(); - String[] columns = {SQLiteAxolotlStore.KEY}; - String[] selectionArgs = {account.getUuid()}; - Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, - columns, - SQLiteAxolotlStore.ACCOUNT + "=?", - selectionArgs, - null, null, null); - - while (cursor.moveToNext()) { - try { - prekeys.add(new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT))); - } catch (IOException ignored) { - } - } - cursor.close(); - return prekeys; - } - - public boolean containsSignedPreKey(Account account, int signedPreKeyId) { - Cursor cursor = getCursorForPreKey(account, signedPreKeyId); - int count = cursor.getCount(); - cursor.close(); - return count != 0; - } - - public void storeSignedPreKey(Account account, SignedPreKeyRecord record) { - SQLiteDatabase db = this.getWritableDatabase(); - ContentValues values = new ContentValues(); - values.put(SQLiteAxolotlStore.ID, record.getId()); - values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(), Base64.DEFAULT)); - values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); - db.insert(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, null, values); - } - - public void deleteSignedPreKey(Account account, int signedPreKeyId) { - SQLiteDatabase db = this.getWritableDatabase(); - String[] args = {account.getUuid(), Integer.toString(signedPreKeyId)}; - db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, - SQLiteAxolotlStore.ACCOUNT + "=? AND " - + SQLiteAxolotlStore.ID + "=?", - args); - } - - private Cursor getIdentityKeyCursor(Account account, String name, boolean own) { - final SQLiteDatabase db = this.getReadableDatabase(); - return getIdentityKeyCursor(db, account, name, own); - } - - private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String name, boolean own) { - return getIdentityKeyCursor(db, account, name, own, null); - } - - private Cursor getIdentityKeyCursor(Account account, String fingerprint) { - final SQLiteDatabase db = this.getReadableDatabase(); - return getIdentityKeyCursor(db, account, fingerprint); - } - - private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String fingerprint) { - return getIdentityKeyCursor(db, account, null, null, fingerprint); - } - - private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String name, Boolean own, String fingerprint) { - String[] columns = {SQLiteAxolotlStore.TRUST, - SQLiteAxolotlStore.ACTIVE, - SQLiteAxolotlStore.KEY}; - ArrayList selectionArgs = new ArrayList<>(4); - selectionArgs.add(account.getUuid()); - String selectionString = SQLiteAxolotlStore.ACCOUNT + " = ?"; - if (name != null) { - selectionArgs.add(name); - selectionString += " AND " + SQLiteAxolotlStore.NAME + " = ?"; - } - if (fingerprint != null) { - selectionArgs.add(fingerprint); - selectionString += " AND " + SQLiteAxolotlStore.FINGERPRINT + " = ?"; - } - if (own != null) { - selectionArgs.add(own ? "1" : "0"); - selectionString += " AND " + SQLiteAxolotlStore.OWN + " = ?"; - } - Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME, - columns, - selectionString, - selectionArgs.toArray(new String[selectionArgs.size()]), - null, null, null); - - return cursor; - } - - public IdentityKeyPair loadOwnIdentityKeyPair(Account account) { - SQLiteDatabase db = getReadableDatabase(); - return loadOwnIdentityKeyPair(db, account); - } - - private IdentityKeyPair loadOwnIdentityKeyPair(SQLiteDatabase db, Account account) { - String name = account.getJid().toBareJid().toPreppedString(); - IdentityKeyPair identityKeyPair = null; - Cursor cursor = getIdentityKeyCursor(db, account, name, true); - if (cursor.getCount() != 0) { - cursor.moveToFirst(); - try { - identityKeyPair = new IdentityKeyPair(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT)); - } catch (InvalidKeyException e) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name); - } - } - cursor.close(); - - return identityKeyPair; - } - - public Set loadIdentityKeys(Account account, String name) { - return loadIdentityKeys(account, name, null); - } - - public Set loadIdentityKeys(Account account, String name, FingerprintStatus status) { - Set identityKeys = new HashSet<>(); - Cursor cursor = getIdentityKeyCursor(account, name, false); - - while (cursor.moveToNext()) { - if (status != null && !FingerprintStatus.fromCursor(cursor).equals(status)) { - continue; - } - try { - String key = cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)); - if (key != null) { - identityKeys.add(new IdentityKey(Base64.decode(key, Base64.DEFAULT), 0)); - } else { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Missing key (possibly preverified) in database for account" + account.getJid().toBareJid() + ", address: " + name); - } - } catch (InvalidKeyException e) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name); - } - } - cursor.close(); - - return identityKeys; - } - - public long numTrustedKeys(Account account, String name) { - SQLiteDatabase db = getReadableDatabase(); - String[] args = { - account.getUuid(), - name, - FingerprintStatus.Trust.TRUSTED.toString(), - FingerprintStatus.Trust.VERIFIED.toString(), - FingerprintStatus.Trust.VERIFIED_X509.toString() - }; - return DatabaseUtils.queryNumEntries(db, SQLiteAxolotlStore.IDENTITIES_TABLENAME, - SQLiteAxolotlStore.ACCOUNT + " = ?" - + " AND " + SQLiteAxolotlStore.NAME + " = ?" - + " AND (" + SQLiteAxolotlStore.TRUST + " = ? OR " + SQLiteAxolotlStore.TRUST + " = ? OR " +SQLiteAxolotlStore.TRUST +" = ?)" - + " AND " +SQLiteAxolotlStore.ACTIVE + " > 0", - args - ); - } - - private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized, FingerprintStatus status) { - SQLiteDatabase db = this.getWritableDatabase(); - ContentValues values = new ContentValues(); - values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); - values.put(SQLiteAxolotlStore.NAME, name); - values.put(SQLiteAxolotlStore.OWN, own ? 1 : 0); - values.put(SQLiteAxolotlStore.FINGERPRINT, fingerprint); - values.put(SQLiteAxolotlStore.KEY, base64Serialized); - values.putAll(status.toContentValues()); - String where = SQLiteAxolotlStore.ACCOUNT+"=? AND "+SQLiteAxolotlStore.NAME+"=? AND "+SQLiteAxolotlStore.FINGERPRINT+" =?"; - String[] whereArgs = {account.getUuid(),name,fingerprint}; - int rows = db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME,values,where,whereArgs); - if (rows == 0) { - db.insert(SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values); - } - } - - public void storePreVerification(Account account, String name, String fingerprint, FingerprintStatus status) { - SQLiteDatabase db = this.getWritableDatabase(); - ContentValues values = new ContentValues(); - values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); - values.put(SQLiteAxolotlStore.NAME, name); - values.put(SQLiteAxolotlStore.OWN, 0); - values.put(SQLiteAxolotlStore.FINGERPRINT, fingerprint); - values.putAll(status.toContentValues()); - db.insert(SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values); - } - - public FingerprintStatus getFingerprintStatus(Account account, String fingerprint) { - Cursor cursor = getIdentityKeyCursor(account, fingerprint); - final FingerprintStatus status; - if (cursor.getCount() > 0) { - cursor.moveToFirst(); - status = FingerprintStatus.fromCursor(cursor); - } else { - status = null; - } - cursor.close(); - return status; - } - - public boolean setIdentityKeyTrust(Account account, String fingerprint, FingerprintStatus fingerprintStatus) { - SQLiteDatabase db = this.getWritableDatabase(); - return setIdentityKeyTrust(db, account, fingerprint, fingerprintStatus); - } - - private boolean setIdentityKeyTrust(SQLiteDatabase db, Account account, String fingerprint, FingerprintStatus status) { - String[] selectionArgs = { - account.getUuid(), - fingerprint - }; - int rows = db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, status.toContentValues(), - SQLiteAxolotlStore.ACCOUNT + " = ? AND " - + SQLiteAxolotlStore.FINGERPRINT + " = ? ", - selectionArgs); - return rows == 1; - } - - public boolean setIdentityKeyCertificate(Account account, String fingerprint, X509Certificate x509Certificate) { - SQLiteDatabase db = this.getWritableDatabase(); - String[] selectionArgs = { - account.getUuid(), - fingerprint - }; - try { - ContentValues values = new ContentValues(); - values.put(SQLiteAxolotlStore.CERTIFICATE, x509Certificate.getEncoded()); - return db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values, - SQLiteAxolotlStore.ACCOUNT + " = ? AND " - + SQLiteAxolotlStore.FINGERPRINT + " = ? ", - selectionArgs) == 1; - } catch (CertificateEncodingException e) { - Log.d(Config.LOGTAG, "could not encode certificate"); - return false; - } - } - - public X509Certificate getIdentityKeyCertifcate(Account account, String fingerprint) { - SQLiteDatabase db = this.getReadableDatabase(); - String[] selectionArgs = { - account.getUuid(), - fingerprint - }; - String[] colums = {SQLiteAxolotlStore.CERTIFICATE}; - String selection = SQLiteAxolotlStore.ACCOUNT + " = ? AND " + SQLiteAxolotlStore.FINGERPRINT + " = ? "; - Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME, colums, selection, selectionArgs, null, null, null); - if (cursor.getCount() < 1) { - return null; - } else { - cursor.moveToFirst(); - byte[] certificate = cursor.getBlob(cursor.getColumnIndex(SQLiteAxolotlStore.CERTIFICATE)); - cursor.close(); - if (certificate == null || certificate.length == 0) { - return null; - } - try { - CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); - return (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(certificate)); - } catch (CertificateException e) { - Log.d(Config.LOGTAG,"certificate exception "+e.getMessage()); - return null; - } - } - } - - public void storeIdentityKey(Account account, String name, IdentityKey identityKey, FingerprintStatus status) { - storeIdentityKey(account, name, false, identityKey.getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT), status); - } - - public void storeOwnIdentityKeyPair(Account account, IdentityKeyPair identityKeyPair) { - storeIdentityKey(account, account.getJid().toBareJid().toPreppedString(), true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), FingerprintStatus.createActiveVerified(false)); - } - - public void recreateAxolotlDb(SQLiteDatabase db) { - Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + ">>> (RE)CREATING AXOLOTL DATABASE <<<"); - db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SESSION_TABLENAME); - db.execSQL(CREATE_SESSIONS_STATEMENT); - db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.PREKEY_TABLENAME); - db.execSQL(CREATE_PREKEYS_STATEMENT); - db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME); - db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT); - db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.IDENTITIES_TABLENAME); - db.execSQL(CREATE_IDENTITIES_STATEMENT); - } - - public void wipeAxolotlDb(Account account) { - String accountName = account.getUuid(); - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ">>> WIPING AXOLOTL DATABASE FOR ACCOUNT " + accountName + " <<<"); - SQLiteDatabase db = this.getWritableDatabase(); - String[] deleteArgs = { - accountName - }; - db.delete(SQLiteAxolotlStore.SESSION_TABLENAME, - SQLiteAxolotlStore.ACCOUNT + " = ?", - deleteArgs); - db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME, - SQLiteAxolotlStore.ACCOUNT + " = ?", - deleteArgs); - db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, - SQLiteAxolotlStore.ACCOUNT + " = ?", - deleteArgs); - db.delete(SQLiteAxolotlStore.IDENTITIES_TABLENAME, - SQLiteAxolotlStore.ACCOUNT + " = ?", - deleteArgs); - } - - public boolean startTimeCountExceedsThreshold() { - SQLiteDatabase db = this.getWritableDatabase(); - long cleanBeforeTimestamp = System.currentTimeMillis() - Config.FREQUENT_RESTARTS_DETECTION_WINDOW; - db.execSQL("delete from "+START_TIMES_TABLE+" where timestamp < "+cleanBeforeTimestamp); - ContentValues values = new ContentValues(); - values.put("timestamp",System.currentTimeMillis()); - db.insert(START_TIMES_TABLE,null,values); - String[] columns = new String[]{"count(timestamp)"}; - Cursor cursor = db.query(START_TIMES_TABLE,columns,null,null,null,null,null); - int count; - if (cursor.moveToFirst()) { - count = cursor.getInt(0); - } else { - count = 0; - } - cursor.close(); - Log.d(Config.LOGTAG,"start time counter reached "+count); - return count >= Config.FREQUENT_RESTARTS_THRESHOLD; - } - - public void clearStartTimeCounter() { - Log.d(Config.LOGTAG,"resetting start time counter"); - SQLiteDatabase db = this.getWritableDatabase(); - db.execSQL("delete from " + START_TIMES_TABLE); - } + if (oldVersion < 17 && newVersion >= 17) { + List accounts = getAccounts(db); + for (Account account : accounts) { + String ownDeviceIdString = account.getKey(SQLiteAxolotlStore.JSONKEY_REGISTRATION_ID); + if (ownDeviceIdString == null) { + continue; + } + int ownDeviceId = Integer.valueOf(ownDeviceIdString); + AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), ownDeviceId); + deleteSession(db, account, ownAddress); + IdentityKeyPair identityKeyPair = loadOwnIdentityKeyPair(db, account); + if (identityKeyPair != null) { + String[] selectionArgs = { + account.getUuid(), + identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", "") + }; + ContentValues values = new ContentValues(); + values.put(SQLiteAxolotlStore.TRUSTED, 2); + db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values, + SQLiteAxolotlStore.ACCOUNT + " = ? AND " + + SQLiteAxolotlStore.FINGERPRINT + " = ? ", + selectionArgs); + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not load own identity key pair"); + } + } + } + if (oldVersion < 18 && newVersion >= 18) { + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.READ + " NUMBER DEFAULT 1"); + } + + if (oldVersion < 21 && newVersion >= 21) { + List accounts = getAccounts(db); + for (Account account : accounts) { + account.unsetPgpSignature(); + db.update(Account.TABLENAME, account.getContentValues(), Account.UUID + + "=?", new String[]{account.getUuid()}); + } + } + + if (oldVersion < 23 && newVersion >= 23) { + db.execSQL(CREATE_DISCOVERY_RESULTS_STATEMENT); + } + + if (oldVersion < 24 && newVersion >= 24) { + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.EDITED + " TEXT"); + } + + if (oldVersion < 25 && newVersion >= 25) { + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.OOB + " INTEGER"); + } + + if (oldVersion < 26 && newVersion >= 26) { + db.execSQL(CREATE_PRESENCE_TEMPLATES_STATEMENT); + } + + if (oldVersion < 27 && newVersion >= 27) { + db.execSQL("DELETE FROM " + ServiceDiscoveryResult.TABLENAME); + } + + if (oldVersion < 28 && newVersion >= 28) { + canonicalizeJids(db); + } + + if (oldVersion < 29 && newVersion >= 29) { + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.ERROR_MESSAGE + " TEXT"); + } + + if (oldVersion < 30 && newVersion >= 30) { + db.execSQL(CREATE_START_TIMES_TABLE); + } + if (oldVersion < 31 && newVersion >= 31) { + db.execSQL("ALTER TABLE " + SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN " + SQLiteAxolotlStore.TRUST + " TEXT"); + db.execSQL("ALTER TABLE " + SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN " + SQLiteAxolotlStore.ACTIVE + " NUMBER"); + HashMap migration = new HashMap<>(); + migration.put(0, createFingerprintStatusContentValues(FingerprintStatus.Trust.UNDECIDED, true)); + migration.put(1, createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED, true)); + migration.put(2, createFingerprintStatusContentValues(FingerprintStatus.Trust.UNTRUSTED, true)); + migration.put(3, createFingerprintStatusContentValues(FingerprintStatus.Trust.COMPROMISED, false)); + migration.put(4, createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED, false)); + migration.put(5, createFingerprintStatusContentValues(FingerprintStatus.Trust.UNDECIDED, false)); + migration.put(6, createFingerprintStatusContentValues(FingerprintStatus.Trust.UNTRUSTED, false)); + migration.put(7, createFingerprintStatusContentValues(FingerprintStatus.Trust.VERIFIED_X509, true)); + migration.put(8, createFingerprintStatusContentValues(FingerprintStatus.Trust.VERIFIED_X509, false)); + for (Map.Entry entry : migration.entrySet()) { + String whereClause = SQLiteAxolotlStore.TRUSTED + "=?"; + String[] where = {String.valueOf(entry.getKey())}; + db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, entry.getValue(), whereClause, where); + } + } + } + + private static ContentValues createFingerprintStatusContentValues(FingerprintStatus.Trust trust, boolean active) { + ContentValues values = new ContentValues(); + values.put(SQLiteAxolotlStore.TRUST, trust.toString()); + values.put(SQLiteAxolotlStore.ACTIVE, active ? 1 : 0); + return values; + } + + private void canonicalizeJids(SQLiteDatabase db) { + // migrate db to new, canonicalized JID domainpart representation + + // Conversation table + Cursor cursor = db.rawQuery("select * from " + Conversation.TABLENAME, new String[0]); + while (cursor.moveToNext()) { + String newJid; + try { + newJid = Jid.fromString( + cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID)) + ).toPreppedString(); + } catch (InvalidJidException ignored) { + Log.e(Config.LOGTAG, "Failed to migrate Conversation CONTACTJID " + + cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID)) + + ": " + ignored + ". Skipping..."); + continue; + } + + String updateArgs[] = { + newJid, + cursor.getString(cursor.getColumnIndex(Conversation.UUID)), + }; + db.execSQL("update " + Conversation.TABLENAME + + " set " + Conversation.CONTACTJID + " = ? " + + " where " + Conversation.UUID + " = ?", updateArgs); + } + cursor.close(); + + // Contact table + cursor = db.rawQuery("select * from " + Contact.TABLENAME, new String[0]); + while (cursor.moveToNext()) { + String newJid; + try { + newJid = Jid.fromString( + cursor.getString(cursor.getColumnIndex(Contact.JID)) + ).toPreppedString(); + } catch (InvalidJidException ignored) { + Log.e(Config.LOGTAG, "Failed to migrate Contact JID " + + cursor.getString(cursor.getColumnIndex(Contact.JID)) + + ": " + ignored + ". Skipping..."); + continue; + } + + String updateArgs[] = { + newJid, + cursor.getString(cursor.getColumnIndex(Contact.ACCOUNT)), + cursor.getString(cursor.getColumnIndex(Contact.JID)), + }; + db.execSQL("update " + Contact.TABLENAME + + " set " + Contact.JID + " = ? " + + " where " + Contact.ACCOUNT + " = ? " + + " AND " + Contact.JID + " = ?", updateArgs); + } + cursor.close(); + + // Account table + cursor = db.rawQuery("select * from " + Account.TABLENAME, new String[0]); + while (cursor.moveToNext()) { + String newServer; + try { + newServer = Jid.fromParts( + cursor.getString(cursor.getColumnIndex(Account.USERNAME)), + cursor.getString(cursor.getColumnIndex(Account.SERVER)), + "mobile" + ).getDomainpart(); + } catch (InvalidJidException ignored) { + Log.e(Config.LOGTAG, "Failed to migrate Account SERVER " + + cursor.getString(cursor.getColumnIndex(Account.SERVER)) + + ": " + ignored + ". Skipping..."); + continue; + } + + String updateArgs[] = { + newServer, + cursor.getString(cursor.getColumnIndex(Account.UUID)), + }; + db.execSQL("update " + Account.TABLENAME + + " set " + Account.SERVER + " = ? " + + " where " + Account.UUID + " = ?", updateArgs); + } + cursor.close(); + } + + public static synchronized DatabaseBackend getInstance(Context context) { + if (instance == null) { + instance = new DatabaseBackend(context); + } + return instance; + } + + public void createConversation(Conversation conversation) { + SQLiteDatabase db = this.getWritableDatabase(); + db.insert(Conversation.TABLENAME, null, conversation.getContentValues()); + } + + public void createMessage(Message message) { + SQLiteDatabase db = this.getWritableDatabase(); + db.insert(Message.TABLENAME, null, message.getContentValues()); + } + + public void createAccount(Account account) { + SQLiteDatabase db = this.getWritableDatabase(); + db.insert(Account.TABLENAME, null, account.getContentValues()); + } + + public void insertDiscoveryResult(ServiceDiscoveryResult result) { + SQLiteDatabase db = this.getWritableDatabase(); + db.insert(ServiceDiscoveryResult.TABLENAME, null, result.getContentValues()); + } + + public ServiceDiscoveryResult findDiscoveryResult(final String hash, final String ver) { + SQLiteDatabase db = this.getReadableDatabase(); + String[] selectionArgs = {hash, ver}; + Cursor cursor = db.query(ServiceDiscoveryResult.TABLENAME, null, + ServiceDiscoveryResult.HASH + "=? AND " + ServiceDiscoveryResult.VER + "=?", + selectionArgs, null, null, null); + if (cursor.getCount() == 0) { + cursor.close(); + return null; + } + cursor.moveToFirst(); + + ServiceDiscoveryResult result = null; + try { + result = new ServiceDiscoveryResult(cursor); + } catch (JSONException e) { /* result is still null */ } + + cursor.close(); + return result; + } + + public void insertPresenceTemplate(PresenceTemplate template) { + SQLiteDatabase db = this.getWritableDatabase(); + db.insert(PresenceTemplate.TABELNAME, null, template.getContentValues()); + } + + public List getPresenceTemplates() { + ArrayList templates = new ArrayList<>(); + SQLiteDatabase db = this.getReadableDatabase(); + Cursor cursor = db.query(PresenceTemplate.TABELNAME, null, null, null, null, null, PresenceTemplate.LAST_USED + " desc"); + while (cursor.moveToNext()) { + templates.add(PresenceTemplate.fromCursor(cursor)); + } + cursor.close(); + return templates; + } + + public void deletePresenceTemplate(PresenceTemplate template) { + Log.d(Config.LOGTAG, "deleting presence template with uuid " + template.getUuid()); + SQLiteDatabase db = this.getWritableDatabase(); + String where = PresenceTemplate.UUID + "=?"; + String[] whereArgs = {template.getUuid()}; + db.delete(PresenceTemplate.TABELNAME, where, whereArgs); + } + + public CopyOnWriteArrayList getConversations(int status) { + CopyOnWriteArrayList list = new CopyOnWriteArrayList<>(); + SQLiteDatabase db = this.getReadableDatabase(); + String[] selectionArgs = {Integer.toString(status)}; + Cursor cursor = db.rawQuery("select * from " + Conversation.TABLENAME + + " where " + Conversation.STATUS + " = ? order by " + + Conversation.CREATED + " desc", selectionArgs); + while (cursor.moveToNext()) { + list.add(Conversation.fromCursor(cursor)); + } + cursor.close(); + return list; + } + + public ArrayList getMessages(Conversation conversations, int limit) { + return getMessages(conversations, limit, -1); + } + + public ArrayList getMessages(Conversation conversation, int limit, + long timestamp) { + ArrayList list = new ArrayList<>(); + SQLiteDatabase db = this.getReadableDatabase(); + Cursor cursor; + if (timestamp == -1) { + String[] selectionArgs = {conversation.getUuid()}; + cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION + + "=?", selectionArgs, null, null, Message.TIME_SENT + + " DESC", String.valueOf(limit)); + } else { + String[] selectionArgs = {conversation.getUuid(), + Long.toString(timestamp)}; + cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION + + "=? and " + Message.TIME_SENT + " 0) { + cursor.moveToLast(); + do { + Message message = Message.fromCursor(cursor); + message.setConversation(conversation); + list.add(message); + } while (cursor.moveToPrevious()); + } + cursor.close(); + return list; + } + + public Iterable getMessagesIterable(final Conversation conversation) { + return new Iterable() { + @Override + public Iterator iterator() { + class MessageIterator implements Iterator { + SQLiteDatabase db = getReadableDatabase(); + String[] selectionArgs = {conversation.getUuid()}; + Cursor cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION + + "=?", selectionArgs, null, null, Message.TIME_SENT + + " ASC", null); + + public MessageIterator() { + cursor.moveToFirst(); + } + + @Override + public boolean hasNext() { + return !cursor.isAfterLast(); + } + + @Override + public Message next() { + Message message = Message.fromCursor(cursor); + cursor.moveToNext(); + return message; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + return new MessageIterator(); + } + }; + } + + public Conversation findConversation(final Account account, final Jid contactJid) { + SQLiteDatabase db = this.getReadableDatabase(); + String[] selectionArgs = {account.getUuid(), + contactJid.toBareJid().toPreppedString() + "/%", + contactJid.toBareJid().toPreppedString() + }; + Cursor cursor = db.query(Conversation.TABLENAME, null, + Conversation.ACCOUNT + "=? AND (" + Conversation.CONTACTJID + + " like ? OR " + Conversation.CONTACTJID + "=?)", selectionArgs, null, null, null); + if (cursor.getCount() == 0) { + cursor.close(); + return null; + } + cursor.moveToFirst(); + Conversation conversation = Conversation.fromCursor(cursor); + cursor.close(); + return conversation; + } + + public void updateConversation(final Conversation conversation) { + final SQLiteDatabase db = this.getWritableDatabase(); + final String[] args = {conversation.getUuid()}; + db.update(Conversation.TABLENAME, conversation.getContentValues(), + Conversation.UUID + "=?", args); + } + + public List getAccounts() { + SQLiteDatabase db = this.getReadableDatabase(); + return getAccounts(db); + } + + private List getAccounts(SQLiteDatabase db) { + List list = new ArrayList<>(); + Cursor cursor = db.query(Account.TABLENAME, null, null, null, null, + null, null); + while (cursor.moveToNext()) { + list.add(Account.fromCursor(cursor)); + } + cursor.close(); + return list; + } + + public boolean updateAccount(Account account) { + SQLiteDatabase db = this.getWritableDatabase(); + String[] args = {account.getUuid()}; + final int rows = db.update(Account.TABLENAME, account.getContentValues(), Account.UUID + "=?", args); + return rows == 1; + } + + public boolean deleteAccount(Account account) { + SQLiteDatabase db = this.getWritableDatabase(); + String[] args = {account.getUuid()}; + final int rows = db.delete(Account.TABLENAME, Account.UUID + "=?", args); + return rows == 1; + } + + public boolean hasEnabledAccounts() { + SQLiteDatabase db = this.getReadableDatabase(); + Cursor cursor = db.rawQuery("select count(" + Account.UUID + ") from " + + Account.TABLENAME + " where not options & (1 <<1)", null); + try { + cursor.moveToFirst(); + int count = cursor.getInt(0); + return (count > 0); + } catch (SQLiteCantOpenDatabaseException e) { + return true; // better safe than sorry + } catch (RuntimeException e) { + return true; // better safe than sorry + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + + @Override + public SQLiteDatabase getWritableDatabase() { + SQLiteDatabase db = super.getWritableDatabase(); + db.execSQL("PRAGMA foreign_keys=ON;"); + return db; + } + + public void updateMessage(Message message) { + SQLiteDatabase db = this.getWritableDatabase(); + String[] args = {message.getUuid()}; + db.update(Message.TABLENAME, message.getContentValues(), Message.UUID + + "=?", args); + } + + public void updateMessage(Message message, String uuid) { + SQLiteDatabase db = this.getWritableDatabase(); + String[] args = {uuid}; + db.update(Message.TABLENAME, message.getContentValues(), Message.UUID + + "=?", args); + } + + public void readRoster(Roster roster) { + SQLiteDatabase db = this.getReadableDatabase(); + Cursor cursor; + String args[] = {roster.getAccount().getUuid()}; + cursor = db.query(Contact.TABLENAME, null, Contact.ACCOUNT + "=?", args, null, null, null); + while (cursor.moveToNext()) { + roster.initContact(Contact.fromCursor(cursor)); + } + cursor.close(); + } + + public void writeRoster(final Roster roster) { + final Account account = roster.getAccount(); + final SQLiteDatabase db = this.getWritableDatabase(); + db.beginTransaction(); + for (Contact contact : roster.getContacts()) { + if (contact.getOption(Contact.Options.IN_ROSTER)) { + db.insert(Contact.TABLENAME, null, contact.getContentValues()); + } else { + String where = Contact.ACCOUNT + "=? AND " + Contact.JID + "=?"; + String[] whereArgs = {account.getUuid(), contact.getJid().toPreppedString()}; + db.delete(Contact.TABLENAME, where, whereArgs); + } + } + db.setTransactionSuccessful(); + db.endTransaction(); + account.setRosterVersion(roster.getVersion()); + updateAccount(account); + } + + public void deleteMessagesInConversation(Conversation conversation) { + SQLiteDatabase db = this.getWritableDatabase(); + String[] args = {conversation.getUuid()}; + db.delete(Message.TABLENAME, Message.CONVERSATION + "=?", args); + } + + public Pair getLastMessageReceived(Account account) { + Cursor cursor = null; + try { + SQLiteDatabase db = this.getReadableDatabase(); + String sql = "select messages.timeSent,messages.serverMsgId from accounts join conversations on accounts.uuid=conversations.accountUuid join messages on conversations.uuid=messages.conversationUuid where accounts.uuid=? and (messages.status=0 or messages.carbon=1 or messages.serverMsgId not null) order by messages.timesent desc limit 1"; + String[] args = {account.getUuid()}; + cursor = db.rawQuery(sql, args); + if (cursor.getCount() == 0) { + return null; + } else { + cursor.moveToFirst(); + return new Pair<>(cursor.getLong(0), cursor.getString(1)); + } + } catch (Exception e) { + return null; + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + + public Pair getLastClearDate(Account account) { + SQLiteDatabase db = this.getReadableDatabase(); + String[] columns = {Conversation.ATTRIBUTES}; + String selection = Conversation.ACCOUNT + "=?"; + String[] args = {account.getUuid()}; + Cursor cursor = db.query(Conversation.TABLENAME, columns, selection, args, null, null, null); + long maxClearDate = 0; + while (cursor.moveToNext()) { + try { + final JSONObject jsonObject = new JSONObject(cursor.getString(0)); + maxClearDate = Math.max(maxClearDate, jsonObject.getLong(Conversation.ATTRIBUTE_LAST_CLEAR_HISTORY)); + } catch (Exception e) { + //ignored + } + } + cursor.close(); + return new Pair<>(maxClearDate, null); + } + + private Cursor getCursorForSession(Account account, AxolotlAddress contact) { + final SQLiteDatabase db = this.getReadableDatabase(); + String[] selectionArgs = {account.getUuid(), + contact.getName(), + Integer.toString(contact.getDeviceId())}; + return db.query(SQLiteAxolotlStore.SESSION_TABLENAME, + null, + SQLiteAxolotlStore.ACCOUNT + " = ? AND " + + SQLiteAxolotlStore.NAME + " = ? AND " + + SQLiteAxolotlStore.DEVICE_ID + " = ? ", + selectionArgs, + null, null, null); + } + + public SessionRecord loadSession(Account account, AxolotlAddress contact) { + SessionRecord session = null; + Cursor cursor = getCursorForSession(account, contact); + if (cursor.getCount() != 0) { + cursor.moveToFirst(); + try { + session = new SessionRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT)); + } catch (IOException e) { + cursor.close(); + throw new AssertionError(e); + } + } + cursor.close(); + return session; + } + + public List getSubDeviceSessions(Account account, AxolotlAddress contact) { + final SQLiteDatabase db = this.getReadableDatabase(); + return getSubDeviceSessions(db, account, contact); + } + + private List getSubDeviceSessions(SQLiteDatabase db, Account account, AxolotlAddress contact) { + List devices = new ArrayList<>(); + String[] columns = {SQLiteAxolotlStore.DEVICE_ID}; + String[] selectionArgs = {account.getUuid(), + contact.getName()}; + Cursor cursor = db.query(SQLiteAxolotlStore.SESSION_TABLENAME, + columns, + SQLiteAxolotlStore.ACCOUNT + " = ? AND " + + SQLiteAxolotlStore.NAME + " = ?", + selectionArgs, + null, null, null); + + while (cursor.moveToNext()) { + devices.add(cursor.getInt( + cursor.getColumnIndex(SQLiteAxolotlStore.DEVICE_ID))); + } + + cursor.close(); + return devices; + } + + public boolean containsSession(Account account, AxolotlAddress contact) { + Cursor cursor = getCursorForSession(account, contact); + int count = cursor.getCount(); + cursor.close(); + return count != 0; + } + + public void storeSession(Account account, AxolotlAddress contact, SessionRecord session) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(SQLiteAxolotlStore.NAME, contact.getName()); + values.put(SQLiteAxolotlStore.DEVICE_ID, contact.getDeviceId()); + values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(session.serialize(), Base64.DEFAULT)); + values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); + db.insert(SQLiteAxolotlStore.SESSION_TABLENAME, null, values); + } + + public void deleteSession(Account account, AxolotlAddress contact) { + SQLiteDatabase db = this.getWritableDatabase(); + deleteSession(db, account, contact); + } + + private void deleteSession(SQLiteDatabase db, Account account, AxolotlAddress contact) { + String[] args = {account.getUuid(), + contact.getName(), + Integer.toString(contact.getDeviceId())}; + db.delete(SQLiteAxolotlStore.SESSION_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + " = ? AND " + + SQLiteAxolotlStore.NAME + " = ? AND " + + SQLiteAxolotlStore.DEVICE_ID + " = ? ", + args); + } + + public void deleteAllSessions(Account account, AxolotlAddress contact) { + SQLiteDatabase db = this.getWritableDatabase(); + String[] args = {account.getUuid(), contact.getName()}; + db.delete(SQLiteAxolotlStore.SESSION_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + "=? AND " + + SQLiteAxolotlStore.NAME + " = ?", + args); + } + + private Cursor getCursorForPreKey(Account account, int preKeyId) { + SQLiteDatabase db = this.getReadableDatabase(); + String[] columns = {SQLiteAxolotlStore.KEY}; + String[] selectionArgs = {account.getUuid(), Integer.toString(preKeyId)}; + Cursor cursor = db.query(SQLiteAxolotlStore.PREKEY_TABLENAME, + columns, + SQLiteAxolotlStore.ACCOUNT + "=? AND " + + SQLiteAxolotlStore.ID + "=?", + selectionArgs, + null, null, null); + + return cursor; + } + + public PreKeyRecord loadPreKey(Account account, int preKeyId) { + PreKeyRecord record = null; + Cursor cursor = getCursorForPreKey(account, preKeyId); + if (cursor.getCount() != 0) { + cursor.moveToFirst(); + try { + record = new PreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT)); + } catch (IOException e) { + throw new AssertionError(e); + } + } + cursor.close(); + return record; + } + + public boolean containsPreKey(Account account, int preKeyId) { + Cursor cursor = getCursorForPreKey(account, preKeyId); + int count = cursor.getCount(); + cursor.close(); + return count != 0; + } + + public void storePreKey(Account account, PreKeyRecord record) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(SQLiteAxolotlStore.ID, record.getId()); + values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(), Base64.DEFAULT)); + values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); + db.insert(SQLiteAxolotlStore.PREKEY_TABLENAME, null, values); + } + + public void deletePreKey(Account account, int preKeyId) { + SQLiteDatabase db = this.getWritableDatabase(); + String[] args = {account.getUuid(), Integer.toString(preKeyId)}; + db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + "=? AND " + + SQLiteAxolotlStore.ID + "=?", + args); + } + + private Cursor getCursorForSignedPreKey(Account account, int signedPreKeyId) { + SQLiteDatabase db = this.getReadableDatabase(); + String[] columns = {SQLiteAxolotlStore.KEY}; + String[] selectionArgs = {account.getUuid(), Integer.toString(signedPreKeyId)}; + Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + columns, + SQLiteAxolotlStore.ACCOUNT + "=? AND " + SQLiteAxolotlStore.ID + "=?", + selectionArgs, + null, null, null); + + return cursor; + } + + public SignedPreKeyRecord loadSignedPreKey(Account account, int signedPreKeyId) { + SignedPreKeyRecord record = null; + Cursor cursor = getCursorForSignedPreKey(account, signedPreKeyId); + if (cursor.getCount() != 0) { + cursor.moveToFirst(); + try { + record = new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT)); + } catch (IOException e) { + throw new AssertionError(e); + } + } + cursor.close(); + return record; + } + + public List loadSignedPreKeys(Account account) { + List prekeys = new ArrayList<>(); + SQLiteDatabase db = this.getReadableDatabase(); + String[] columns = {SQLiteAxolotlStore.KEY}; + String[] selectionArgs = {account.getUuid()}; + Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + columns, + SQLiteAxolotlStore.ACCOUNT + "=?", + selectionArgs, + null, null, null); + + while (cursor.moveToNext()) { + try { + prekeys.add(new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT))); + } catch (IOException ignored) { + } + } + cursor.close(); + return prekeys; + } + + public boolean containsSignedPreKey(Account account, int signedPreKeyId) { + Cursor cursor = getCursorForPreKey(account, signedPreKeyId); + int count = cursor.getCount(); + cursor.close(); + return count != 0; + } + + public void storeSignedPreKey(Account account, SignedPreKeyRecord record) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(SQLiteAxolotlStore.ID, record.getId()); + values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(), Base64.DEFAULT)); + values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); + db.insert(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, null, values); + } + + public void deleteSignedPreKey(Account account, int signedPreKeyId) { + SQLiteDatabase db = this.getWritableDatabase(); + String[] args = {account.getUuid(), Integer.toString(signedPreKeyId)}; + db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + "=? AND " + + SQLiteAxolotlStore.ID + "=?", + args); + } + + private Cursor getIdentityKeyCursor(Account account, String name, boolean own) { + final SQLiteDatabase db = this.getReadableDatabase(); + return getIdentityKeyCursor(db, account, name, own); + } + + private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String name, boolean own) { + return getIdentityKeyCursor(db, account, name, own, null); + } + + private Cursor getIdentityKeyCursor(Account account, String fingerprint) { + final SQLiteDatabase db = this.getReadableDatabase(); + return getIdentityKeyCursor(db, account, fingerprint); + } + + private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String fingerprint) { + return getIdentityKeyCursor(db, account, null, null, fingerprint); + } + + private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String name, Boolean own, String fingerprint) { + String[] columns = {SQLiteAxolotlStore.TRUST, + SQLiteAxolotlStore.ACTIVE, + SQLiteAxolotlStore.KEY}; + ArrayList selectionArgs = new ArrayList<>(4); + selectionArgs.add(account.getUuid()); + String selectionString = SQLiteAxolotlStore.ACCOUNT + " = ?"; + if (name != null) { + selectionArgs.add(name); + selectionString += " AND " + SQLiteAxolotlStore.NAME + " = ?"; + } + if (fingerprint != null) { + selectionArgs.add(fingerprint); + selectionString += " AND " + SQLiteAxolotlStore.FINGERPRINT + " = ?"; + } + if (own != null) { + selectionArgs.add(own ? "1" : "0"); + selectionString += " AND " + SQLiteAxolotlStore.OWN + " = ?"; + } + Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME, + columns, + selectionString, + selectionArgs.toArray(new String[selectionArgs.size()]), + null, null, null); + + return cursor; + } + + public IdentityKeyPair loadOwnIdentityKeyPair(Account account) { + SQLiteDatabase db = getReadableDatabase(); + return loadOwnIdentityKeyPair(db, account); + } + + private IdentityKeyPair loadOwnIdentityKeyPair(SQLiteDatabase db, Account account) { + String name = account.getJid().toBareJid().toPreppedString(); + IdentityKeyPair identityKeyPair = null; + Cursor cursor = getIdentityKeyCursor(db, account, name, true); + if (cursor.getCount() != 0) { + cursor.moveToFirst(); + try { + identityKeyPair = new IdentityKeyPair(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT)); + } catch (InvalidKeyException e) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name); + } + } + cursor.close(); + + return identityKeyPair; + } + + public Set loadIdentityKeys(Account account, String name) { + return loadIdentityKeys(account, name, null); + } + + public Set loadIdentityKeys(Account account, String name, FingerprintStatus status) { + Set identityKeys = new HashSet<>(); + Cursor cursor = getIdentityKeyCursor(account, name, false); + + while (cursor.moveToNext()) { + if (status != null && !FingerprintStatus.fromCursor(cursor).equals(status)) { + continue; + } + try { + String key = cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)); + if (key != null) { + identityKeys.add(new IdentityKey(Base64.decode(key, Base64.DEFAULT), 0)); + } else { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Missing key (possibly preverified) in database for account" + account.getJid().toBareJid() + ", address: " + name); + } + } catch (InvalidKeyException e) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name); + } + } + cursor.close(); + + return identityKeys; + } + + public long numTrustedKeys(Account account, String name) { + SQLiteDatabase db = getReadableDatabase(); + String[] args = { + account.getUuid(), + name, + FingerprintStatus.Trust.TRUSTED.toString(), + FingerprintStatus.Trust.VERIFIED.toString(), + FingerprintStatus.Trust.VERIFIED_X509.toString() + }; + return DatabaseUtils.queryNumEntries(db, SQLiteAxolotlStore.IDENTITIES_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + " = ?" + + " AND " + SQLiteAxolotlStore.NAME + " = ?" + + " AND (" + SQLiteAxolotlStore.TRUST + " = ? OR " + SQLiteAxolotlStore.TRUST + " = ? OR " + SQLiteAxolotlStore.TRUST + " = ?)" + + " AND " + SQLiteAxolotlStore.ACTIVE + " > 0", + args + ); + } + + private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized, FingerprintStatus status) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); + values.put(SQLiteAxolotlStore.NAME, name); + values.put(SQLiteAxolotlStore.OWN, own ? 1 : 0); + values.put(SQLiteAxolotlStore.FINGERPRINT, fingerprint); + values.put(SQLiteAxolotlStore.KEY, base64Serialized); + values.putAll(status.toContentValues()); + String where = SQLiteAxolotlStore.ACCOUNT + "=? AND " + SQLiteAxolotlStore.NAME + "=? AND " + SQLiteAxolotlStore.FINGERPRINT + " =?"; + String[] whereArgs = {account.getUuid(), name, fingerprint}; + int rows = db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values, where, whereArgs); + if (rows == 0) { + db.insert(SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values); + } + } + + public void storePreVerification(Account account, String name, String fingerprint, FingerprintStatus status) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); + values.put(SQLiteAxolotlStore.NAME, name); + values.put(SQLiteAxolotlStore.OWN, 0); + values.put(SQLiteAxolotlStore.FINGERPRINT, fingerprint); + values.putAll(status.toContentValues()); + db.insert(SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values); + } + + public FingerprintStatus getFingerprintStatus(Account account, String fingerprint) { + Cursor cursor = getIdentityKeyCursor(account, fingerprint); + final FingerprintStatus status; + if (cursor.getCount() > 0) { + cursor.moveToFirst(); + status = FingerprintStatus.fromCursor(cursor); + } else { + status = null; + } + cursor.close(); + return status; + } + + public boolean setIdentityKeyTrust(Account account, String fingerprint, FingerprintStatus fingerprintStatus) { + SQLiteDatabase db = this.getWritableDatabase(); + return setIdentityKeyTrust(db, account, fingerprint, fingerprintStatus); + } + + private boolean setIdentityKeyTrust(SQLiteDatabase db, Account account, String fingerprint, FingerprintStatus status) { + String[] selectionArgs = { + account.getUuid(), + fingerprint + }; + int rows = db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, status.toContentValues(), + SQLiteAxolotlStore.ACCOUNT + " = ? AND " + + SQLiteAxolotlStore.FINGERPRINT + " = ? ", + selectionArgs); + return rows == 1; + } + + public boolean setIdentityKeyCertificate(Account account, String fingerprint, X509Certificate x509Certificate) { + SQLiteDatabase db = this.getWritableDatabase(); + String[] selectionArgs = { + account.getUuid(), + fingerprint + }; + try { + ContentValues values = new ContentValues(); + values.put(SQLiteAxolotlStore.CERTIFICATE, x509Certificate.getEncoded()); + return db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values, + SQLiteAxolotlStore.ACCOUNT + " = ? AND " + + SQLiteAxolotlStore.FINGERPRINT + " = ? ", + selectionArgs) == 1; + } catch (CertificateEncodingException e) { + Log.d(Config.LOGTAG, "could not encode certificate"); + return false; + } + } + + public X509Certificate getIdentityKeyCertifcate(Account account, String fingerprint) { + SQLiteDatabase db = this.getReadableDatabase(); + String[] selectionArgs = { + account.getUuid(), + fingerprint + }; + String[] colums = {SQLiteAxolotlStore.CERTIFICATE}; + String selection = SQLiteAxolotlStore.ACCOUNT + " = ? AND " + SQLiteAxolotlStore.FINGERPRINT + " = ? "; + Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME, colums, selection, selectionArgs, null, null, null); + if (cursor.getCount() < 1) { + return null; + } else { + cursor.moveToFirst(); + byte[] certificate = cursor.getBlob(cursor.getColumnIndex(SQLiteAxolotlStore.CERTIFICATE)); + cursor.close(); + if (certificate == null || certificate.length == 0) { + return null; + } + try { + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + return (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(certificate)); + } catch (CertificateException e) { + Log.d(Config.LOGTAG, "certificate exception " + e.getMessage()); + return null; + } + } + } + + public void storeIdentityKey(Account account, String name, IdentityKey identityKey, FingerprintStatus status) { + storeIdentityKey(account, name, false, identityKey.getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT), status); + } + + public void storeOwnIdentityKeyPair(Account account, IdentityKeyPair identityKeyPair) { + storeIdentityKey(account, account.getJid().toBareJid().toPreppedString(), true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), FingerprintStatus.createActiveVerified(false)); + } + + public void recreateAxolotlDb(SQLiteDatabase db) { + Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + ">>> (RE)CREATING AXOLOTL DATABASE <<<"); + db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SESSION_TABLENAME); + db.execSQL(CREATE_SESSIONS_STATEMENT); + db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.PREKEY_TABLENAME); + db.execSQL(CREATE_PREKEYS_STATEMENT); + db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME); + db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT); + db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.IDENTITIES_TABLENAME); + db.execSQL(CREATE_IDENTITIES_STATEMENT); + } + + public void wipeAxolotlDb(Account account) { + String accountName = account.getUuid(); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ">>> WIPING AXOLOTL DATABASE FOR ACCOUNT " + accountName + " <<<"); + SQLiteDatabase db = this.getWritableDatabase(); + String[] deleteArgs = { + accountName + }; + db.delete(SQLiteAxolotlStore.SESSION_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + " = ?", + deleteArgs); + db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + " = ?", + deleteArgs); + db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + " = ?", + deleteArgs); + db.delete(SQLiteAxolotlStore.IDENTITIES_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + " = ?", + deleteArgs); + } + + public boolean startTimeCountExceedsThreshold() { + SQLiteDatabase db = this.getWritableDatabase(); + long cleanBeforeTimestamp = System.currentTimeMillis() - Config.FREQUENT_RESTARTS_DETECTION_WINDOW; + db.execSQL("delete from " + START_TIMES_TABLE + " where timestamp < " + cleanBeforeTimestamp); + ContentValues values = new ContentValues(); + values.put("timestamp", System.currentTimeMillis()); + db.insert(START_TIMES_TABLE, null, values); + String[] columns = new String[]{"count(timestamp)"}; + Cursor cursor = db.query(START_TIMES_TABLE, columns, null, null, null, null, null); + int count; + if (cursor.moveToFirst()) { + count = cursor.getInt(0); + } else { + count = 0; + } + cursor.close(); + Log.d(Config.LOGTAG, "start time counter reached " + count); + return count >= Config.FREQUENT_RESTARTS_THRESHOLD; + } + + public void clearStartTimeCounter() { + Log.d(Config.LOGTAG, "resetting start time counter"); + SQLiteDatabase db = this.getWritableDatabase(); + db.execSQL("delete from " + START_TIMES_TABLE); + } } diff --git a/src/main/java/de/pixart/messenger/persistance/FileBackend.java b/src/main/java/de/pixart/messenger/persistance/FileBackend.java index 9babae1de..893834ad7 100644 --- a/src/main/java/de/pixart/messenger/persistance/FileBackend.java +++ b/src/main/java/de/pixart/messenger/persistance/FileBackend.java @@ -58,918 +58,918 @@ import de.pixart.messenger.utils.FileWriterException; import de.pixart.messenger.xmpp.pep.Avatar; public class FileBackend { - private static final SimpleDateFormat fileDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US); - - public static final String CONVERSATIONS_FILE_PROVIDER = "de.pixart.messenger.files"; - - private XmppConnectionService mXmppConnectionService; - - public FileBackend(XmppConnectionService service) { - this.mXmppConnectionService = service; - } - - private void createNoMedia() { - final File nomedia_files = new File(getConversationsFileDirectory()+".nomedia"); - final File nomedia_audios = new File(getConversationsAudioDirectory()+".nomedia"); - if (!nomedia_files.exists()) { - try { - nomedia_files.createNewFile(); - } catch (Exception e) { - Log.d(Config.LOGTAG, "could not create nomedia file for files directory"); - } - } - if (!nomedia_audios.exists()) { - try { - nomedia_audios.createNewFile(); - } catch (Exception e) { - Log.d(Config.LOGTAG, "could not create nomedia file for audio directory"); - } - } - } - - public void updateMediaScanner(File file) { - if (file.getAbsolutePath().startsWith(getConversationsImageDirectory()) - || file.getAbsolutePath().startsWith(getConversationsVideoDirectory())) { - Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); - intent.setData(Uri.fromFile(file)); - mXmppConnectionService.sendBroadcast(intent); - } else { - createNoMedia(); - } - } - - public boolean deleteFile(Message message) { - File file = getFile(message); - if (file.delete()) { - updateMediaScanner(file); - return true; - } else { - return false; - } - } - - public DownloadableFile getFile(Message message) { - return getFile(message, true); - } - - public DownloadableFile getFile(Message message, boolean decrypted) { - final boolean encrypted = !decrypted - && (message.getEncryption() == Message.ENCRYPTION_PGP - || message.getEncryption() == Message.ENCRYPTION_DECRYPTED); - final DownloadableFile file; - String path = message.getRelativeFilePath(); - if (path == null) { - String filename = fileDateFormat.format(new Date(message.getTimeSent()))+"_"+message.getUuid().substring(0,4); - path = filename; - } - if (path.startsWith("/")) { - file = new DownloadableFile(path); - } else { - String mime = message.getMimeType(); - if (mime != null && mime.startsWith("image")) { - file = new DownloadableFile(getConversationsImageDirectory() + path); - } else if (mime != null && mime.startsWith("video")) { - file = new DownloadableFile(getConversationsVideoDirectory() + path); - } else if (mime != null && mime.startsWith("audio")) { - file = new DownloadableFile(getConversationsAudioDirectory() + path); - } else { - file = new DownloadableFile(getConversationsFileDirectory() + path); - } - } - if (encrypted) { - return new DownloadableFile(getConversationsFileDirectory() + file.getName() + ".pgp"); - } else { - return file; - } - } - - private static long getFileSize(Context context, Uri uri) { - Cursor cursor = context.getContentResolver().query(uri, null, null, null, null); - if (cursor != null && cursor.moveToFirst()) { - return cursor.getLong(cursor.getColumnIndex(OpenableColumns.SIZE)); - } else { - return -1; - } - } - - public static boolean allFilesUnderSize(Context context, List uris, long max) { - if (max <= 0) { - Log.d(Config.LOGTAG,"server did not report max file size for http upload"); - return true; //exception to be compatible with HTTP Upload < v0.2 - } - for(Uri uri : uris) { - if (FileBackend.getFileSize(context, uri) > max) { - Log.d(Config.LOGTAG,"not all files are under "+max+" bytes. suggesting falling back to jingle"); - return false; - } - } - return true; - } - - public static String getConversationsFileDirectory() { - return Environment.getExternalStorageDirectory().getAbsolutePath()+"/Pix-Art Messenger/files/"; - } - - public static String getConversationsImageDirectory() { - return Environment.getExternalStorageDirectory().getAbsolutePath()+"/Pix-Art Messenger/images/"; - } - - public static String getConversationsVideoDirectory() { - return Environment.getExternalStorageDirectory().getAbsolutePath()+"/Pix-Art Messenger/videos/"; - } - - public static String getConversationsAudioDirectory() { - return Environment.getExternalStorageDirectory().getAbsolutePath()+"/Pix-Art Messenger/audios/"; - } - - public static String getConversationsDirectory() { - return Environment.getExternalStorageDirectory().getAbsolutePath()+"/Pix-Art Messenger/"; - } - - public Bitmap resize(Bitmap originalBitmap, int size) { - int w = originalBitmap.getWidth(); - int h = originalBitmap.getHeight(); - if (Math.max(w, h) > size) { - int scalledW; - int scalledH; - if (w <= h) { - scalledW = (int) (w / ((double) h / size)); - scalledH = size; - } else { - scalledW = size; - scalledH = (int) (h / ((double) w / size)); - } - Bitmap result = Bitmap.createScaledBitmap(originalBitmap, scalledW, scalledH, true); - if (originalBitmap != null && !originalBitmap.isRecycled()) { - originalBitmap.recycle(); - } - return result; - } else { - return originalBitmap; - } - } - - public static Bitmap rotate(Bitmap bitmap, int degree) { - if (degree == 0) { - return bitmap; - } - int w = bitmap.getWidth(); - int h = bitmap.getHeight(); - Matrix mtx = new Matrix(); - mtx.postRotate(degree); - Bitmap result = Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true); - if (bitmap != null && !bitmap.isRecycled()) { - bitmap.recycle(); - } - return result; - } - - public boolean useImageAsIs(Uri uri) { - String path = getOriginalPath(uri); - if (path == null) { - return false; - } - File file = new File(path); - long size = file.length(); - if (size == 0 || size >= Config.IMAGE_MAX_SIZE ) { - return false; - } - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - try { - BitmapFactory.decodeStream(mXmppConnectionService.getContentResolver().openInputStream(uri), null, options); - if (options == null || options.outMimeType == null || options.outHeight <= 0 || options.outWidth <= 0) { - return false; - } - return (options.outWidth <= Config.IMAGE_SIZE && options.outHeight <= Config.IMAGE_SIZE && options.outMimeType.contains(Config.IMAGE_FORMAT.name().toLowerCase())); - } catch (FileNotFoundException e) { - return false; - } - } - - public String getOriginalPath(Uri uri) { - return FileUtils.getPath(mXmppConnectionService,uri); - } - - public void copyFileToPrivateStorage(File file, Uri uri) throws FileCopyException { - Log.d(Config.LOGTAG,"copy file ("+uri.toString()+") to private storage "+file.getAbsolutePath()); - file.getParentFile().mkdirs(); - OutputStream os = null; - InputStream is = null; - try { - file.createNewFile(); - os = new FileOutputStream(file); - is = mXmppConnectionService.getContentResolver().openInputStream(uri); - byte[] buffer = new byte[1024]; - int length; - while ((length = is.read(buffer)) > 0) { - try { - os.write(buffer, 0, length); - } catch (IOException e) { - throw new FileWriterException(); - } - } - try { - os.flush(); - } catch (IOException e) { - throw new FileWriterException(); - } - } catch(FileNotFoundException e) { - throw new FileCopyException(R.string.error_file_not_found); - } catch(FileWriterException e) { - throw new FileCopyException(R.string.error_unable_to_create_temporary_file); - } catch (IOException e) { - e.printStackTrace(); - throw new FileCopyException(R.string.error_io_exception); - } finally { - close(os); - close(is); - } - } - - public void copyFileToPrivateStorage(Message message, Uri uri) throws FileCopyException { - String mime = mXmppConnectionService.getContentResolver().getType(uri); - Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage (mime="+mime+")"); - String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime); - if (extension == null) { - extension = getExtensionFromUri(uri); - } - String filename = fileDateFormat.format(new Date(message.getTimeSent()))+"_"+message.getUuid().substring(0,4); - message.setRelativeFilePath(filename + "." + extension); - copyFileToPrivateStorage(mXmppConnectionService.getFileBackend().getFile(message), uri); - } - - private String getExtensionFromUri(Uri uri) { - String[] projection = {MediaStore.MediaColumns.DATA}; - String filename = null; - Cursor cursor = mXmppConnectionService.getContentResolver().query(uri, projection, null, null, null); - if (cursor != null) { - try { - if (cursor.moveToFirst()) { - filename = cursor.getString(0); - } - } catch (Exception e) { - filename = null; - } finally { - cursor.close(); - } - } - int pos = filename == null ? -1 : filename.lastIndexOf('.'); - return pos > 0 ? filename.substring(pos+1) : null; - } - - private void copyImageToPrivateStorage(File file, Uri image, int sampleSize) throws FileCopyException { - file.getParentFile().mkdirs(); - InputStream is = null; - OutputStream os = null; - try { - if (!file.exists() && !file.createNewFile()) { - throw new FileCopyException(R.string.error_unable_to_create_temporary_file); - } - is = mXmppConnectionService.getContentResolver().openInputStream(image); - if (is == null) { - throw new FileCopyException(R.string.error_not_an_image_file); - } - Bitmap originalBitmap; - BitmapFactory.Options options = new BitmapFactory.Options(); - int inSampleSize = (int) Math.pow(2, sampleSize); - Log.d(Config.LOGTAG, "reading bitmap with sample size " + inSampleSize); - options.inSampleSize = inSampleSize; - originalBitmap = BitmapFactory.decodeStream(is, null, options); - is.close(); - if (originalBitmap == null) { - throw new FileCopyException(R.string.error_not_an_image_file); - } - Bitmap scaledBitmap = resize(originalBitmap, Config.IMAGE_SIZE); - int rotation = getRotation(image); - scaledBitmap = rotate(scaledBitmap, rotation); - boolean targetSizeReached = false; - int quality = Config.IMAGE_QUALITY; - while(!targetSizeReached) { - os = new FileOutputStream(file); - boolean success = scaledBitmap.compress(Config.IMAGE_FORMAT, quality, os); - if (!success) { - throw new FileCopyException(R.string.error_compressing_image); - } - os.flush(); - targetSizeReached = file.length() <= Config.IMAGE_MAX_SIZE || quality <= 50; - quality -= 5; - } - scaledBitmap.recycle(); - } catch (FileNotFoundException e) { - throw new FileCopyException(R.string.error_file_not_found); - } catch (IOException e) { - e.printStackTrace(); - throw new FileCopyException(R.string.error_io_exception); - } catch (SecurityException e) { - throw new FileCopyException(R.string.error_security_exception_during_image_copy); - } catch (OutOfMemoryError e) { - ++sampleSize; - if (sampleSize <= 3) { - copyImageToPrivateStorage(file, image, sampleSize); - } else { - throw new FileCopyException(R.string.error_out_of_memory); - } - } finally { - close(os); - close(is); - } - } - - public void copyImageToPrivateStorage(File file, Uri image) throws FileCopyException { - Log.d(Config.LOGTAG,"copy image ("+image.toString()+") to private storage "+file.getAbsolutePath()); - copyImageToPrivateStorage(file, image, 0); - } - - public void copyImageToPrivateStorage(Message message, Uri image) throws FileCopyException { - String filename = fileDateFormat.format(new Date(message.getTimeSent()))+"_"+message.getUuid().substring(0,4); - switch(Config.IMAGE_FORMAT) { - case JPEG: - message.setRelativeFilePath(filename+".jpg"); - break; - case PNG: - message.setRelativeFilePath(filename+".png"); - break; - case WEBP: - message.setRelativeFilePath(filename+".webp"); - break; - } - copyImageToPrivateStorage(getFile(message), image); - updateFileParams(message); - } - - private int getRotation(File file) { - return getRotation(Uri.parse("file://"+file.getAbsolutePath())); - } - - private int getRotation(Uri image) { - InputStream is = null; - try { - is = mXmppConnectionService.getContentResolver().openInputStream(image); - return ExifHelper.getOrientation(is); - } catch (FileNotFoundException e) { - return 0; - } finally { - close(is); - } - } - - public Bitmap getThumbnail(Message message, int size, boolean cacheOnly) throws FileNotFoundException { - final String uuid = message.getUuid(); - final LruCache cache = mXmppConnectionService.getBitmapCache(); - Bitmap thumbnail = cache.get(uuid); - if ((thumbnail == null) && (!cacheOnly)) { - synchronized (cache) { - thumbnail = cache.get(uuid); - if (thumbnail != null) { - return thumbnail; - } - DownloadableFile file = getFile(message); - if (file.getMimeType().startsWith("video/")) { - thumbnail = getVideoPreview(file, size); - } else { - Bitmap fullsize = getFullsizeImagePreview(file, size); - if (fullsize == null) { - throw new FileNotFoundException(); - } - thumbnail = resize(fullsize, size); - thumbnail = rotate(thumbnail, getRotation(file)); - } - this.mXmppConnectionService.getBitmapCache().put(uuid, thumbnail); - } - } - return thumbnail; - } - - private Bitmap getFullsizeImagePreview(File file, int size) { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inSampleSize = calcSampleSize(file, size); - try { - return BitmapFactory.decodeFile(file.getAbsolutePath(), options); - } catch (OutOfMemoryError e) { - options.inSampleSize *= 2; - return BitmapFactory.decodeFile(file.getAbsolutePath(), options); - } - } - - private Bitmap getVideoPreview(File file, int size) { - MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever(); - Bitmap frame; - try { - metadataRetriever.setDataSource(file.getAbsolutePath()); - frame = metadataRetriever.getFrameAtTime(0); - metadataRetriever.release(); - frame = resize(frame, size); - } catch(IllegalArgumentException | NullPointerException e) { - frame = Bitmap.createBitmap(size,size, Bitmap.Config.ARGB_8888); - frame.eraseColor(0xff000000); - } - Canvas canvas = new Canvas(frame); - Bitmap play = BitmapFactory.decodeResource(mXmppConnectionService.getResources(), R.drawable.play_video); - float x = (frame.getWidth() - play.getWidth()) / 2.0f; - float y = (frame.getHeight() - play.getHeight()) / 2.0f; - canvas.drawBitmap(play,x,y,null); - return frame; - } - - private static String getTakePhotoPath() { - return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)+"/Camera/"; - } - - public Uri getTakePhotoUri() { - File file = new File(getTakePhotoPath()+"IMG_" + fileDateFormat.format(new Date()) + ".jpg"); - file.getParentFile().mkdirs(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - return FileProvider.getUriForFile(mXmppConnectionService, CONVERSATIONS_FILE_PROVIDER, file); - } else { - return Uri.fromFile(file); - } - } - - public static Uri getIndexableTakePhotoUri(Uri original) { - if ("file".equals(original.getScheme())) { - return original; - } else { - List segments = original.getPathSegments(); - return Uri.parse("file://"+getTakePhotoPath()+segments.get(segments.size() - 1)); - } - } - - public Avatar getPepAvatar(Uri image, int size, Bitmap.CompressFormat format) { - try { - Avatar avatar = new Avatar(); - Bitmap bm = cropCenterSquare(image, size); - if (bm == null) { - return null; - } - ByteArrayOutputStream mByteArrayOutputStream = new ByteArrayOutputStream(); - Base64OutputStream mBase64OutputStream = new Base64OutputStream( - mByteArrayOutputStream, Base64.DEFAULT); - MessageDigest digest = MessageDigest.getInstance("SHA-1"); - DigestOutputStream mDigestOutputStream = new DigestOutputStream( - mBase64OutputStream, digest); - if (!bm.compress(format, 75, mDigestOutputStream)) { - return null; - } - mDigestOutputStream.flush(); - mDigestOutputStream.close(); - avatar.sha1sum = CryptoHelper.bytesToHex(digest.digest()); - avatar.image = new String(mByteArrayOutputStream.toByteArray()); - return avatar; - } catch (NoSuchAlgorithmException e) { - return null; - } catch (IOException e) { - return null; - } - } - - public 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 { - close(is); - } - } - - public boolean isAvatarCached(Avatar avatar) { - File file = new File(getAvatarPath(avatar.getFilename())); - return file.exists(); - } - - public boolean save(Avatar avatar) { - File file; - if (isAvatarCached(avatar)) { - file = new File(getAvatarPath(avatar.getFilename())); - } else { - String filename = getAvatarPath(avatar.getFilename()); - file = new File(filename + ".tmp"); - file.getParentFile().mkdirs(); - OutputStream os = null; - try { - file.createNewFile(); - os = new FileOutputStream(file); - MessageDigest digest = MessageDigest.getInstance("SHA-1"); - digest.reset(); - DigestOutputStream mDigestOutputStream = new DigestOutputStream(os, digest); - mDigestOutputStream.write(avatar.getImageAsBytes()); - mDigestOutputStream.flush(); - mDigestOutputStream.close(); - String sha1sum = CryptoHelper.bytesToHex(digest.digest()); - if (sha1sum.equals(avatar.sha1sum)) { - file.renameTo(new File(filename)); - } else { - Log.d(Config.LOGTAG, "sha1sum mismatch for " + avatar.owner); - file.delete(); - return false; - } - } catch (IllegalArgumentException | IOException | NoSuchAlgorithmException e) { - return false; - } finally { - close(os); - } - } - avatar.size = file.length(); - return true; - } - - public String getAvatarPath(String avatar) { - return mXmppConnectionService.getFilesDir().getAbsolutePath()+ "/avatars/" + avatar; - } - - public Uri getAvatarUri(String avatar) { - return Uri.parse("file:" + getAvatarPath(avatar)); - } - - public Bitmap cropCenterSquare(Uri image, int size) { - if (image == null) { - return null; - } - InputStream is = null; - try { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inSampleSize = calcSampleSize(image, size); - is = mXmppConnectionService.getContentResolver().openInputStream(image); - if (is == null) { - return null; - } - Bitmap input = BitmapFactory.decodeStream(is, null, options); - if (input == null) { - return null; - } else { - input = rotate(input, getRotation(image)); - return cropCenterSquare(input, size); - } - } catch (SecurityException e) { - return null; // happens for example on Android 6.0 if contacts permissions get revoked - } catch (FileNotFoundException e) { - return null; - } finally { - close(is); - } - } - - public Bitmap cropCenter(Uri image, int newHeight, int newWidth) { - if (image == null) { - return null; - } - InputStream is = null; - try { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inSampleSize = calcSampleSize(image, Math.max(newHeight, newWidth)); - is = mXmppConnectionService.getContentResolver().openInputStream(image); - if (is == null) { - return null; - } - Bitmap source = BitmapFactory.decodeStream(is, null, options); - if (source == null) { - return null; - } - int sourceWidth = source.getWidth(); - int sourceHeight = source.getHeight(); - float xScale = (float) newWidth / sourceWidth; - float yScale = (float) newHeight / sourceHeight; - float scale = Math.max(xScale, yScale); - float scaledWidth = scale * sourceWidth; - float scaledHeight = scale * sourceHeight; - float left = (newWidth - scaledWidth) / 2; - float top = (newHeight - scaledHeight) / 2; - - RectF targetRect = new RectF(left, top, left + scaledWidth, top + scaledHeight); - Bitmap dest = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(dest); - canvas.drawBitmap(source, null, targetRect, null); - if (source != null && !source.isRecycled()) { - source.recycle(); - } - return dest; - } catch (SecurityException e) { - return null; //android 6.0 with revoked permissions for example - } catch (FileNotFoundException e) { - return null; - } finally { - close(is); - } - } - - public Bitmap cropCenterSquare(Bitmap input, int size) { - int w = input.getWidth(); - int h = input.getHeight(); - - float scale = Math.max((float) size / h, (float) size / w); - - float outWidth = scale * w; - float outHeight = scale * h; - float left = (size - outWidth) / 2; - float top = (size - outHeight) / 2; - RectF target = new RectF(left, top, left + outWidth, top + outHeight); - - Bitmap output = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(output); - canvas.drawBitmap(input, null, target, null); - if (input != null && !input.isRecycled()) { - input.recycle(); - } - return output; - } - - private int calcSampleSize(Uri image, int size) throws FileNotFoundException, SecurityException { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeStream(mXmppConnectionService.getContentResolver().openInputStream(image), null, options); - return calcSampleSize(options, size); - } - - private static int calcSampleSize(File image, int size) { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeFile(image.getAbsolutePath(), options); - return calcSampleSize(options, size); - } - - public static int calcSampleSize(BitmapFactory.Options options, int size) { - int height = options.outHeight; - int width = options.outWidth; - int inSampleSize = 1; - - if (height > size || width > size) { - int halfHeight = height / 2; - int halfWidth = width / 2; - - while ((halfHeight / inSampleSize) > size - && (halfWidth / inSampleSize) > size) { - inSampleSize *= 2; - } - } - return inSampleSize; - } - - public Uri getJingleFileUri(Message message) { - File file = getFile(message); - return Uri.parse("file://" + file.getAbsolutePath()); - } - - public void updateFileParams(Message message) { - updateFileParams(message,null); - } - - public void updateFileParams(Message message, URL url) { - DownloadableFile file = getFile(message); - final String mime = file.getMimeType(); - boolean image = message.getType() == Message.TYPE_IMAGE || (mime != null && mime.startsWith("image/")); - boolean video = mime != null && mime.startsWith("video/"); - if (image || video) { - try { - Dimensions dimensions = image ? getImageDimensions(file) : getVideoDimensions(file); - if (url == null) { - message.setBody(Long.toString(file.getSize()) + '|' + dimensions.width + '|' + dimensions.height); - } else { - message.setBody(url.toString() + "|" + Long.toString(file.getSize()) + '|' + dimensions.width + '|' + dimensions.height); - } - return; - } catch (NotAVideoFile notAVideoFile) { - Log.d(Config.LOGTAG,"file with mime type "+file.getMimeType()+" was not a video file"); - //fall threw - } - } - if (url != null) { - message.setBody(url.toString()+"|"+Long.toString(file.getSize())); - } else { - message.setBody(Long.toString(file.getSize())); - } - - } - - private Dimensions getImageDimensions(File file) { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeFile(file.getAbsolutePath(), options); - int rotation = getRotation(file); - boolean rotated = rotation == 90 || rotation == 270; - int imageHeight = rotated ? options.outWidth : options.outHeight; - int imageWidth = rotated ? options.outHeight : options.outWidth; - return new Dimensions(imageHeight, imageWidth); - } - - private Dimensions getVideoDimensions(File file) throws NotAVideoFile { - MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever(); - try { - metadataRetriever.setDataSource(file.getAbsolutePath()); - } catch (Exception e) { - throw new NotAVideoFile(); - } - String hasVideo = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO); - if (hasVideo == null) { - throw new NotAVideoFile(); - } - int rotation = extractRotationFromMediaRetriever(metadataRetriever); - boolean rotated = rotation == 90 || rotation == 270; - int height; - try { - String h = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT); - height = Integer.parseInt(h); - } catch (Exception e) { - height = -1; - } - int width; - try { - String w = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH); - width = Integer.parseInt(w); - } catch (Exception e) { - width = -1; - } - metadataRetriever.release(); - Log.d(Config.LOGTAG,"extracted video dims "+width+"x"+height); - return rotated ? new Dimensions(width, height) : new Dimensions(height, width); - } - - private int extractRotationFromMediaRetriever(MediaMetadataRetriever metadataRetriever) { - int rotation; - if (Build.VERSION.SDK_INT >= 17) { - String r = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION); - try { - rotation = Integer.parseInt(r); - } catch (Exception e) { - rotation = 0; - } - } else { - rotation = 0; - } - return rotation; - } - - private class Dimensions { - public final int width; - public final int height; - - public Dimensions(int height, int width) { - this.width = width; - this.height = height; - } - } - - private class NotAVideoFile extends Exception { - - } - - public class FileCopyException extends Exception { - private static final long serialVersionUID = -1010013599132881427L; - private int resId; - - public FileCopyException(int resId) { - this.resId = resId; - } - - public int getResId() { - return resId; - } - } - - public Bitmap getAvatar(String avatar, int size) { - if (avatar == null) { - return null; - } - Bitmap bm = cropCenter(getAvatarUri(avatar), size, size); - if (bm == null) { - return null; - } - return bm; - } - - public boolean isFileAvailable(Message message) { - return getFile(message).exists(); - } - - public static void close(Closeable stream) { - if (stream != null) { - try { - stream.close(); - } catch (IOException e) { - } - } - } - - public static void close(Socket socket) { - if (socket != null) { - try { - socket.close(); - } catch (IOException e) { - } - } - } - - - public static boolean weOwnFile(Context context, Uri uri) { - if (uri == null || !ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { - return false; - } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - return fileIsInFilesDir(context, uri); - } else { - return weOwnFileLollipop(uri); - } - } - - - /** - * This is more than hacky but probably way better than doing nothing - * Further 'optimizations' might contain to get the parents of CacheDir and NoBackupDir - * and check against those as well - */ - private static boolean fileIsInFilesDir(Context context, Uri uri) { - try { - final String haystack = context.getFilesDir().getParentFile().getCanonicalPath(); - final String needle = new File(uri.getPath()).getCanonicalPath(); - return needle.startsWith(haystack); - } catch (IOException e) { - return false; - } - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private static boolean weOwnFileLollipop(Uri uri) { - try { - File file = new File(uri.getPath()); - FileDescriptor fd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY).getFileDescriptor(); - StructStat st = Os.fstat(fd); - return st.st_uid == android.os.Process.myUid(); - } catch (FileNotFoundException e) { - return false; - } catch (Exception e) { - return true; - } - } - - public static Bitmap rotateBitmap(File file, Bitmap bitmap, int orientation) { - - if (orientation == 1) { - return bitmap; - } - - Matrix matrix = new Matrix(); - switch (orientation) { - case 2: - matrix.setScale(-1, 1); - break; - case 3: - matrix.setRotate(180); - break; - case 4: - matrix.setRotate(180); - matrix.postScale(-1, 1); - break; - case 5: - matrix.setRotate(90); - matrix.postScale(-1, 1); - break; - case 6: - matrix.setRotate(90); - break; - case 7: - matrix.setRotate(-90); - matrix.postScale(-1, 1); - break; - case 8: - matrix.setRotate(-90); - break; - default: - return bitmap; - } - - try { - Bitmap oriented = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); - bitmap.recycle(); - return oriented; - } catch (OutOfMemoryError e) { - e.printStackTrace(); - return bitmap; - } - } + private static final SimpleDateFormat fileDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US); + + public static final String CONVERSATIONS_FILE_PROVIDER = "de.pixart.messenger.files"; + + private XmppConnectionService mXmppConnectionService; + + public FileBackend(XmppConnectionService service) { + this.mXmppConnectionService = service; + } + + private void createNoMedia() { + final File nomedia_files = new File(getConversationsFileDirectory() + ".nomedia"); + final File nomedia_audios = new File(getConversationsAudioDirectory() + ".nomedia"); + if (!nomedia_files.exists()) { + try { + nomedia_files.createNewFile(); + } catch (Exception e) { + Log.d(Config.LOGTAG, "could not create nomedia file for files directory"); + } + } + if (!nomedia_audios.exists()) { + try { + nomedia_audios.createNewFile(); + } catch (Exception e) { + Log.d(Config.LOGTAG, "could not create nomedia file for audio directory"); + } + } + } + + public void updateMediaScanner(File file) { + if (file.getAbsolutePath().startsWith(getConversationsImageDirectory()) + || file.getAbsolutePath().startsWith(getConversationsVideoDirectory())) { + Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); + intent.setData(Uri.fromFile(file)); + mXmppConnectionService.sendBroadcast(intent); + } else { + createNoMedia(); + } + } + + public boolean deleteFile(Message message) { + File file = getFile(message); + if (file.delete()) { + updateMediaScanner(file); + return true; + } else { + return false; + } + } + + public DownloadableFile getFile(Message message) { + return getFile(message, true); + } + + public DownloadableFile getFile(Message message, boolean decrypted) { + final boolean encrypted = !decrypted + && (message.getEncryption() == Message.ENCRYPTION_PGP + || message.getEncryption() == Message.ENCRYPTION_DECRYPTED); + final DownloadableFile file; + String path = message.getRelativeFilePath(); + if (path == null) { + String filename = fileDateFormat.format(new Date(message.getTimeSent())) + "_" + message.getUuid().substring(0, 4); + path = filename; + } + if (path.startsWith("/")) { + file = new DownloadableFile(path); + } else { + String mime = message.getMimeType(); + if (mime != null && mime.startsWith("image")) { + file = new DownloadableFile(getConversationsImageDirectory() + path); + } else if (mime != null && mime.startsWith("video")) { + file = new DownloadableFile(getConversationsVideoDirectory() + path); + } else if (mime != null && mime.startsWith("audio")) { + file = new DownloadableFile(getConversationsAudioDirectory() + path); + } else { + file = new DownloadableFile(getConversationsFileDirectory() + path); + } + } + if (encrypted) { + return new DownloadableFile(getConversationsFileDirectory() + file.getName() + ".pgp"); + } else { + return file; + } + } + + private static long getFileSize(Context context, Uri uri) { + Cursor cursor = context.getContentResolver().query(uri, null, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + return cursor.getLong(cursor.getColumnIndex(OpenableColumns.SIZE)); + } else { + return -1; + } + } + + public static boolean allFilesUnderSize(Context context, List uris, long max) { + if (max <= 0) { + Log.d(Config.LOGTAG, "server did not report max file size for http upload"); + return true; //exception to be compatible with HTTP Upload < v0.2 + } + for (Uri uri : uris) { + if (FileBackend.getFileSize(context, uri) > max) { + Log.d(Config.LOGTAG, "not all files are under " + max + " bytes. suggesting falling back to jingle"); + return false; + } + } + return true; + } + + public static String getConversationsFileDirectory() { + return Environment.getExternalStorageDirectory().getAbsolutePath() + "/Pix-Art Messenger/files/"; + } + + public static String getConversationsImageDirectory() { + return Environment.getExternalStorageDirectory().getAbsolutePath() + "/Pix-Art Messenger/images/"; + } + + public static String getConversationsVideoDirectory() { + return Environment.getExternalStorageDirectory().getAbsolutePath() + "/Pix-Art Messenger/videos/"; + } + + public static String getConversationsAudioDirectory() { + return Environment.getExternalStorageDirectory().getAbsolutePath() + "/Pix-Art Messenger/audios/"; + } + + public static String getConversationsDirectory() { + return Environment.getExternalStorageDirectory().getAbsolutePath() + "/Pix-Art Messenger/"; + } + + public Bitmap resize(Bitmap originalBitmap, int size) { + int w = originalBitmap.getWidth(); + int h = originalBitmap.getHeight(); + if (Math.max(w, h) > size) { + int scalledW; + int scalledH; + if (w <= h) { + scalledW = (int) (w / ((double) h / size)); + scalledH = size; + } else { + scalledW = size; + scalledH = (int) (h / ((double) w / size)); + } + Bitmap result = Bitmap.createScaledBitmap(originalBitmap, scalledW, scalledH, true); + if (originalBitmap != null && !originalBitmap.isRecycled()) { + originalBitmap.recycle(); + } + return result; + } else { + return originalBitmap; + } + } + + public static Bitmap rotate(Bitmap bitmap, int degree) { + if (degree == 0) { + return bitmap; + } + int w = bitmap.getWidth(); + int h = bitmap.getHeight(); + Matrix mtx = new Matrix(); + mtx.postRotate(degree); + Bitmap result = Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true); + if (bitmap != null && !bitmap.isRecycled()) { + bitmap.recycle(); + } + return result; + } + + public boolean useImageAsIs(Uri uri) { + String path = getOriginalPath(uri); + if (path == null) { + return false; + } + File file = new File(path); + long size = file.length(); + if (size == 0 || size >= Config.IMAGE_MAX_SIZE) { + return false; + } + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + try { + BitmapFactory.decodeStream(mXmppConnectionService.getContentResolver().openInputStream(uri), null, options); + if (options == null || options.outMimeType == null || options.outHeight <= 0 || options.outWidth <= 0) { + return false; + } + return (options.outWidth <= Config.IMAGE_SIZE && options.outHeight <= Config.IMAGE_SIZE && options.outMimeType.contains(Config.IMAGE_FORMAT.name().toLowerCase())); + } catch (FileNotFoundException e) { + return false; + } + } + + public String getOriginalPath(Uri uri) { + return FileUtils.getPath(mXmppConnectionService, uri); + } + + public void copyFileToPrivateStorage(File file, Uri uri) throws FileCopyException { + Log.d(Config.LOGTAG, "copy file (" + uri.toString() + ") to private storage " + file.getAbsolutePath()); + file.getParentFile().mkdirs(); + OutputStream os = null; + InputStream is = null; + try { + file.createNewFile(); + os = new FileOutputStream(file); + is = mXmppConnectionService.getContentResolver().openInputStream(uri); + byte[] buffer = new byte[1024]; + int length; + while ((length = is.read(buffer)) > 0) { + try { + os.write(buffer, 0, length); + } catch (IOException e) { + throw new FileWriterException(); + } + } + try { + os.flush(); + } catch (IOException e) { + throw new FileWriterException(); + } + } catch (FileNotFoundException e) { + throw new FileCopyException(R.string.error_file_not_found); + } catch (FileWriterException e) { + throw new FileCopyException(R.string.error_unable_to_create_temporary_file); + } catch (IOException e) { + e.printStackTrace(); + throw new FileCopyException(R.string.error_io_exception); + } finally { + close(os); + close(is); + } + } + + public void copyFileToPrivateStorage(Message message, Uri uri) throws FileCopyException { + String mime = mXmppConnectionService.getContentResolver().getType(uri); + Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage (mime=" + mime + ")"); + String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime); + if (extension == null) { + extension = getExtensionFromUri(uri); + } + String filename = fileDateFormat.format(new Date(message.getTimeSent())) + "_" + message.getUuid().substring(0, 4); + message.setRelativeFilePath(filename + "." + extension); + copyFileToPrivateStorage(mXmppConnectionService.getFileBackend().getFile(message), uri); + } + + private String getExtensionFromUri(Uri uri) { + String[] projection = {MediaStore.MediaColumns.DATA}; + String filename = null; + Cursor cursor = mXmppConnectionService.getContentResolver().query(uri, projection, null, null, null); + if (cursor != null) { + try { + if (cursor.moveToFirst()) { + filename = cursor.getString(0); + } + } catch (Exception e) { + filename = null; + } finally { + cursor.close(); + } + } + int pos = filename == null ? -1 : filename.lastIndexOf('.'); + return pos > 0 ? filename.substring(pos + 1) : null; + } + + private void copyImageToPrivateStorage(File file, Uri image, int sampleSize) throws FileCopyException { + file.getParentFile().mkdirs(); + InputStream is = null; + OutputStream os = null; + try { + if (!file.exists() && !file.createNewFile()) { + throw new FileCopyException(R.string.error_unable_to_create_temporary_file); + } + is = mXmppConnectionService.getContentResolver().openInputStream(image); + if (is == null) { + throw new FileCopyException(R.string.error_not_an_image_file); + } + Bitmap originalBitmap; + BitmapFactory.Options options = new BitmapFactory.Options(); + int inSampleSize = (int) Math.pow(2, sampleSize); + Log.d(Config.LOGTAG, "reading bitmap with sample size " + inSampleSize); + options.inSampleSize = inSampleSize; + originalBitmap = BitmapFactory.decodeStream(is, null, options); + is.close(); + if (originalBitmap == null) { + throw new FileCopyException(R.string.error_not_an_image_file); + } + Bitmap scaledBitmap = resize(originalBitmap, Config.IMAGE_SIZE); + int rotation = getRotation(image); + scaledBitmap = rotate(scaledBitmap, rotation); + boolean targetSizeReached = false; + int quality = Config.IMAGE_QUALITY; + while (!targetSizeReached) { + os = new FileOutputStream(file); + boolean success = scaledBitmap.compress(Config.IMAGE_FORMAT, quality, os); + if (!success) { + throw new FileCopyException(R.string.error_compressing_image); + } + os.flush(); + targetSizeReached = file.length() <= Config.IMAGE_MAX_SIZE || quality <= 50; + quality -= 5; + } + scaledBitmap.recycle(); + } catch (FileNotFoundException e) { + throw new FileCopyException(R.string.error_file_not_found); + } catch (IOException e) { + e.printStackTrace(); + throw new FileCopyException(R.string.error_io_exception); + } catch (SecurityException e) { + throw new FileCopyException(R.string.error_security_exception_during_image_copy); + } catch (OutOfMemoryError e) { + ++sampleSize; + if (sampleSize <= 3) { + copyImageToPrivateStorage(file, image, sampleSize); + } else { + throw new FileCopyException(R.string.error_out_of_memory); + } + } finally { + close(os); + close(is); + } + } + + public void copyImageToPrivateStorage(File file, Uri image) throws FileCopyException { + Log.d(Config.LOGTAG, "copy image (" + image.toString() + ") to private storage " + file.getAbsolutePath()); + copyImageToPrivateStorage(file, image, 0); + } + + public void copyImageToPrivateStorage(Message message, Uri image) throws FileCopyException { + String filename = fileDateFormat.format(new Date(message.getTimeSent())) + "_" + message.getUuid().substring(0, 4); + switch (Config.IMAGE_FORMAT) { + case JPEG: + message.setRelativeFilePath(filename + ".jpg"); + break; + case PNG: + message.setRelativeFilePath(filename + ".png"); + break; + case WEBP: + message.setRelativeFilePath(filename + ".webp"); + break; + } + copyImageToPrivateStorage(getFile(message), image); + updateFileParams(message); + } + + private int getRotation(File file) { + return getRotation(Uri.parse("file://" + file.getAbsolutePath())); + } + + private int getRotation(Uri image) { + InputStream is = null; + try { + is = mXmppConnectionService.getContentResolver().openInputStream(image); + return ExifHelper.getOrientation(is); + } catch (FileNotFoundException e) { + return 0; + } finally { + close(is); + } + } + + public Bitmap getThumbnail(Message message, int size, boolean cacheOnly) throws FileNotFoundException { + final String uuid = message.getUuid(); + final LruCache cache = mXmppConnectionService.getBitmapCache(); + Bitmap thumbnail = cache.get(uuid); + if ((thumbnail == null) && (!cacheOnly)) { + synchronized (cache) { + thumbnail = cache.get(uuid); + if (thumbnail != null) { + return thumbnail; + } + DownloadableFile file = getFile(message); + if (file.getMimeType().startsWith("video/")) { + thumbnail = getVideoPreview(file, size); + } else { + Bitmap fullsize = getFullsizeImagePreview(file, size); + if (fullsize == null) { + throw new FileNotFoundException(); + } + thumbnail = resize(fullsize, size); + thumbnail = rotate(thumbnail, getRotation(file)); + } + this.mXmppConnectionService.getBitmapCache().put(uuid, thumbnail); + } + } + return thumbnail; + } + + private Bitmap getFullsizeImagePreview(File file, int size) { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inSampleSize = calcSampleSize(file, size); + try { + return BitmapFactory.decodeFile(file.getAbsolutePath(), options); + } catch (OutOfMemoryError e) { + options.inSampleSize *= 2; + return BitmapFactory.decodeFile(file.getAbsolutePath(), options); + } + } + + private Bitmap getVideoPreview(File file, int size) { + MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever(); + Bitmap frame; + try { + metadataRetriever.setDataSource(file.getAbsolutePath()); + frame = metadataRetriever.getFrameAtTime(0); + metadataRetriever.release(); + frame = resize(frame, size); + } catch (IllegalArgumentException | NullPointerException e) { + frame = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + frame.eraseColor(0xff000000); + } + Canvas canvas = new Canvas(frame); + Bitmap play = BitmapFactory.decodeResource(mXmppConnectionService.getResources(), R.drawable.play_video); + float x = (frame.getWidth() - play.getWidth()) / 2.0f; + float y = (frame.getHeight() - play.getHeight()) / 2.0f; + canvas.drawBitmap(play, x, y, null); + return frame; + } + + private static String getTakePhotoPath() { + return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM) + "/Camera/"; + } + + public Uri getTakePhotoUri() { + File file = new File(getTakePhotoPath() + "IMG_" + fileDateFormat.format(new Date()) + ".jpg"); + file.getParentFile().mkdirs(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return FileProvider.getUriForFile(mXmppConnectionService, CONVERSATIONS_FILE_PROVIDER, file); + } else { + return Uri.fromFile(file); + } + } + + public static Uri getIndexableTakePhotoUri(Uri original) { + if ("file".equals(original.getScheme())) { + return original; + } else { + List segments = original.getPathSegments(); + return Uri.parse("file://" + getTakePhotoPath() + segments.get(segments.size() - 1)); + } + } + + public Avatar getPepAvatar(Uri image, int size, Bitmap.CompressFormat format) { + try { + Avatar avatar = new Avatar(); + Bitmap bm = cropCenterSquare(image, size); + if (bm == null) { + return null; + } + ByteArrayOutputStream mByteArrayOutputStream = new ByteArrayOutputStream(); + Base64OutputStream mBase64OutputStream = new Base64OutputStream( + mByteArrayOutputStream, Base64.DEFAULT); + MessageDigest digest = MessageDigest.getInstance("SHA-1"); + DigestOutputStream mDigestOutputStream = new DigestOutputStream( + mBase64OutputStream, digest); + if (!bm.compress(format, 75, mDigestOutputStream)) { + return null; + } + mDigestOutputStream.flush(); + mDigestOutputStream.close(); + avatar.sha1sum = CryptoHelper.bytesToHex(digest.digest()); + avatar.image = new String(mByteArrayOutputStream.toByteArray()); + return avatar; + } catch (NoSuchAlgorithmException e) { + return null; + } catch (IOException e) { + return null; + } + } + + public 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 { + close(is); + } + } + + public boolean isAvatarCached(Avatar avatar) { + File file = new File(getAvatarPath(avatar.getFilename())); + return file.exists(); + } + + public boolean save(Avatar avatar) { + File file; + if (isAvatarCached(avatar)) { + file = new File(getAvatarPath(avatar.getFilename())); + } else { + String filename = getAvatarPath(avatar.getFilename()); + file = new File(filename + ".tmp"); + file.getParentFile().mkdirs(); + OutputStream os = null; + try { + file.createNewFile(); + os = new FileOutputStream(file); + MessageDigest digest = MessageDigest.getInstance("SHA-1"); + digest.reset(); + DigestOutputStream mDigestOutputStream = new DigestOutputStream(os, digest); + mDigestOutputStream.write(avatar.getImageAsBytes()); + mDigestOutputStream.flush(); + mDigestOutputStream.close(); + String sha1sum = CryptoHelper.bytesToHex(digest.digest()); + if (sha1sum.equals(avatar.sha1sum)) { + file.renameTo(new File(filename)); + } else { + Log.d(Config.LOGTAG, "sha1sum mismatch for " + avatar.owner); + file.delete(); + return false; + } + } catch (IllegalArgumentException | IOException | NoSuchAlgorithmException e) { + return false; + } finally { + close(os); + } + } + avatar.size = file.length(); + return true; + } + + public String getAvatarPath(String avatar) { + return mXmppConnectionService.getFilesDir().getAbsolutePath() + "/avatars/" + avatar; + } + + public Uri getAvatarUri(String avatar) { + return Uri.parse("file:" + getAvatarPath(avatar)); + } + + public Bitmap cropCenterSquare(Uri image, int size) { + if (image == null) { + return null; + } + InputStream is = null; + try { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inSampleSize = calcSampleSize(image, size); + is = mXmppConnectionService.getContentResolver().openInputStream(image); + if (is == null) { + return null; + } + Bitmap input = BitmapFactory.decodeStream(is, null, options); + if (input == null) { + return null; + } else { + input = rotate(input, getRotation(image)); + return cropCenterSquare(input, size); + } + } catch (SecurityException e) { + return null; // happens for example on Android 6.0 if contacts permissions get revoked + } catch (FileNotFoundException e) { + return null; + } finally { + close(is); + } + } + + public Bitmap cropCenter(Uri image, int newHeight, int newWidth) { + if (image == null) { + return null; + } + InputStream is = null; + try { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inSampleSize = calcSampleSize(image, Math.max(newHeight, newWidth)); + is = mXmppConnectionService.getContentResolver().openInputStream(image); + if (is == null) { + return null; + } + Bitmap source = BitmapFactory.decodeStream(is, null, options); + if (source == null) { + return null; + } + int sourceWidth = source.getWidth(); + int sourceHeight = source.getHeight(); + float xScale = (float) newWidth / sourceWidth; + float yScale = (float) newHeight / sourceHeight; + float scale = Math.max(xScale, yScale); + float scaledWidth = scale * sourceWidth; + float scaledHeight = scale * sourceHeight; + float left = (newWidth - scaledWidth) / 2; + float top = (newHeight - scaledHeight) / 2; + + RectF targetRect = new RectF(left, top, left + scaledWidth, top + scaledHeight); + Bitmap dest = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(dest); + canvas.drawBitmap(source, null, targetRect, null); + if (source != null && !source.isRecycled()) { + source.recycle(); + } + return dest; + } catch (SecurityException e) { + return null; //android 6.0 with revoked permissions for example + } catch (FileNotFoundException e) { + return null; + } finally { + close(is); + } + } + + public Bitmap cropCenterSquare(Bitmap input, int size) { + int w = input.getWidth(); + int h = input.getHeight(); + + float scale = Math.max((float) size / h, (float) size / w); + + float outWidth = scale * w; + float outHeight = scale * h; + float left = (size - outWidth) / 2; + float top = (size - outHeight) / 2; + RectF target = new RectF(left, top, left + outWidth, top + outHeight); + + Bitmap output = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(output); + canvas.drawBitmap(input, null, target, null); + if (input != null && !input.isRecycled()) { + input.recycle(); + } + return output; + } + + private int calcSampleSize(Uri image, int size) throws FileNotFoundException, SecurityException { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeStream(mXmppConnectionService.getContentResolver().openInputStream(image), null, options); + return calcSampleSize(options, size); + } + + private static int calcSampleSize(File image, int size) { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(image.getAbsolutePath(), options); + return calcSampleSize(options, size); + } + + public static int calcSampleSize(BitmapFactory.Options options, int size) { + int height = options.outHeight; + int width = options.outWidth; + int inSampleSize = 1; + + if (height > size || width > size) { + int halfHeight = height / 2; + int halfWidth = width / 2; + + while ((halfHeight / inSampleSize) > size + && (halfWidth / inSampleSize) > size) { + inSampleSize *= 2; + } + } + return inSampleSize; + } + + public Uri getJingleFileUri(Message message) { + File file = getFile(message); + return Uri.parse("file://" + file.getAbsolutePath()); + } + + public void updateFileParams(Message message) { + updateFileParams(message, null); + } + + public void updateFileParams(Message message, URL url) { + DownloadableFile file = getFile(message); + final String mime = file.getMimeType(); + boolean image = message.getType() == Message.TYPE_IMAGE || (mime != null && mime.startsWith("image/")); + boolean video = mime != null && mime.startsWith("video/"); + if (image || video) { + try { + Dimensions dimensions = image ? getImageDimensions(file) : getVideoDimensions(file); + if (url == null) { + message.setBody(Long.toString(file.getSize()) + '|' + dimensions.width + '|' + dimensions.height); + } else { + message.setBody(url.toString() + "|" + Long.toString(file.getSize()) + '|' + dimensions.width + '|' + dimensions.height); + } + return; + } catch (NotAVideoFile notAVideoFile) { + Log.d(Config.LOGTAG, "file with mime type " + file.getMimeType() + " was not a video file"); + //fall threw + } + } + if (url != null) { + message.setBody(url.toString() + "|" + Long.toString(file.getSize())); + } else { + message.setBody(Long.toString(file.getSize())); + } + + } + + private Dimensions getImageDimensions(File file) { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(file.getAbsolutePath(), options); + int rotation = getRotation(file); + boolean rotated = rotation == 90 || rotation == 270; + int imageHeight = rotated ? options.outWidth : options.outHeight; + int imageWidth = rotated ? options.outHeight : options.outWidth; + return new Dimensions(imageHeight, imageWidth); + } + + private Dimensions getVideoDimensions(File file) throws NotAVideoFile { + MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever(); + try { + metadataRetriever.setDataSource(file.getAbsolutePath()); + } catch (Exception e) { + throw new NotAVideoFile(); + } + String hasVideo = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO); + if (hasVideo == null) { + throw new NotAVideoFile(); + } + int rotation = extractRotationFromMediaRetriever(metadataRetriever); + boolean rotated = rotation == 90 || rotation == 270; + int height; + try { + String h = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT); + height = Integer.parseInt(h); + } catch (Exception e) { + height = -1; + } + int width; + try { + String w = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH); + width = Integer.parseInt(w); + } catch (Exception e) { + width = -1; + } + metadataRetriever.release(); + Log.d(Config.LOGTAG, "extracted video dims " + width + "x" + height); + return rotated ? new Dimensions(width, height) : new Dimensions(height, width); + } + + private int extractRotationFromMediaRetriever(MediaMetadataRetriever metadataRetriever) { + int rotation; + if (Build.VERSION.SDK_INT >= 17) { + String r = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION); + try { + rotation = Integer.parseInt(r); + } catch (Exception e) { + rotation = 0; + } + } else { + rotation = 0; + } + return rotation; + } + + private class Dimensions { + public final int width; + public final int height; + + public Dimensions(int height, int width) { + this.width = width; + this.height = height; + } + } + + private class NotAVideoFile extends Exception { + + } + + public class FileCopyException extends Exception { + private static final long serialVersionUID = -1010013599132881427L; + private int resId; + + public FileCopyException(int resId) { + this.resId = resId; + } + + public int getResId() { + return resId; + } + } + + public Bitmap getAvatar(String avatar, int size) { + if (avatar == null) { + return null; + } + Bitmap bm = cropCenter(getAvatarUri(avatar), size, size); + if (bm == null) { + return null; + } + return bm; + } + + public boolean isFileAvailable(Message message) { + return getFile(message).exists(); + } + + public static void close(Closeable stream) { + if (stream != null) { + try { + stream.close(); + } catch (IOException e) { + } + } + } + + public static void close(Socket socket) { + if (socket != null) { + try { + socket.close(); + } catch (IOException e) { + } + } + } + + + public static boolean weOwnFile(Context context, Uri uri) { + if (uri == null || !ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { + return false; + } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + return fileIsInFilesDir(context, uri); + } else { + return weOwnFileLollipop(uri); + } + } + + + /** + * This is more than hacky but probably way better than doing nothing + * Further 'optimizations' might contain to get the parents of CacheDir and NoBackupDir + * and check against those as well + */ + private static boolean fileIsInFilesDir(Context context, Uri uri) { + try { + final String haystack = context.getFilesDir().getParentFile().getCanonicalPath(); + final String needle = new File(uri.getPath()).getCanonicalPath(); + return needle.startsWith(haystack); + } catch (IOException e) { + return false; + } + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + private static boolean weOwnFileLollipop(Uri uri) { + try { + File file = new File(uri.getPath()); + FileDescriptor fd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY).getFileDescriptor(); + StructStat st = Os.fstat(fd); + return st.st_uid == android.os.Process.myUid(); + } catch (FileNotFoundException e) { + return false; + } catch (Exception e) { + return true; + } + } + + public static Bitmap rotateBitmap(File file, Bitmap bitmap, int orientation) { + + if (orientation == 1) { + return bitmap; + } + + Matrix matrix = new Matrix(); + switch (orientation) { + case 2: + matrix.setScale(-1, 1); + break; + case 3: + matrix.setRotate(180); + break; + case 4: + matrix.setRotate(180); + matrix.postScale(-1, 1); + break; + case 5: + matrix.setRotate(90); + matrix.postScale(-1, 1); + break; + case 6: + matrix.setRotate(90); + break; + case 7: + matrix.setRotate(-90); + matrix.postScale(-1, 1); + break; + case 8: + matrix.setRotate(-90); + break; + default: + return bitmap; + } + + try { + Bitmap oriented = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); + bitmap.recycle(); + return oriented; + } catch (OutOfMemoryError e) { + e.printStackTrace(); + return bitmap; + } + } } diff --git a/src/main/java/de/pixart/messenger/persistance/OnPhoneContactsMerged.java b/src/main/java/de/pixart/messenger/persistance/OnPhoneContactsMerged.java index 85e852b5d..d5253f227 100644 --- a/src/main/java/de/pixart/messenger/persistance/OnPhoneContactsMerged.java +++ b/src/main/java/de/pixart/messenger/persistance/OnPhoneContactsMerged.java @@ -1,5 +1,5 @@ package de.pixart.messenger.persistance; public interface OnPhoneContactsMerged { - public void phoneContactsMerged(); + public void phoneContactsMerged(); } diff --git a/src/main/java/de/pixart/messenger/services/AbstractConnectionManager.java b/src/main/java/de/pixart/messenger/services/AbstractConnectionManager.java index 6b0bfdc35..fe101edc6 100644 --- a/src/main/java/de/pixart/messenger/services/AbstractConnectionManager.java +++ b/src/main/java/de/pixart/messenger/services/AbstractConnectionManager.java @@ -34,17 +34,17 @@ import de.pixart.messenger.Config; import de.pixart.messenger.entities.DownloadableFile; public class AbstractConnectionManager { - protected XmppConnectionService mXmppConnectionService; + protected XmppConnectionService mXmppConnectionService; - public AbstractConnectionManager(XmppConnectionService service) { - this.mXmppConnectionService = service; - } + public AbstractConnectionManager(XmppConnectionService service) { + this.mXmppConnectionService = service; + } - public XmppConnectionService getXmppConnectionService() { - return this.mXmppConnectionService; - } + public XmppConnectionService getXmppConnectionService() { + return this.mXmppConnectionService; + } - public long getAutoAcceptFileSize() { + public long getAutoAcceptFileSize() { String config = "0"; if (mXmppConnectionService.isWIFI()) { config = this.mXmppConnectionService.getPreferences().getString( @@ -57,96 +57,96 @@ public class AbstractConnectionManager { "auto_accept_file_size_roaming", "1"); } try { - return Long.parseLong(config); - } catch (NumberFormatException e) { - return 1048576; - } - } + return Long.parseLong(config); + } catch (NumberFormatException e) { + return 1048576; + } + } - public boolean hasStoragePermission() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - return mXmppConnectionService.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; - } else { - return true; - } - } + public boolean hasStoragePermission() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return mXmppConnectionService.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; + } else { + return true; + } + } - public static Pair createInputStream(DownloadableFile file, boolean gcm) throws FileNotFoundException { - FileInputStream is; - int size; - is = new FileInputStream(file); - size = (int) file.getSize(); - if (file.getKey() == null) { - return new Pair(is,size); - } - try { - if (gcm) { - AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); - cipher.init(true, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv())); - InputStream cis = new org.bouncycastle.crypto.io.CipherInputStream(is, cipher); - return new Pair<>(cis, cipher.getOutputSize(size)); - } else { - IvParameterSpec ips = new IvParameterSpec(file.getIv()); - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips); - Log.d(Config.LOGTAG, "opening encrypted input stream"); - final int s = Config.REPORT_WRONG_FILESIZE_IN_OTR_JINGLE ? size : (size / 16 + 1) * 16; - return new Pair(new CipherInputStream(is, cipher),s); - } - } catch (InvalidKeyException e) { - return null; - } catch (NoSuchAlgorithmException e) { - return null; - } catch (NoSuchPaddingException e) { - return null; - } catch (InvalidAlgorithmParameterException e) { - return null; - } - } + public static Pair createInputStream(DownloadableFile file, boolean gcm) throws FileNotFoundException { + FileInputStream is; + int size; + is = new FileInputStream(file); + size = (int) file.getSize(); + if (file.getKey() == null) { + return new Pair(is, size); + } + try { + if (gcm) { + AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); + cipher.init(true, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv())); + InputStream cis = new org.bouncycastle.crypto.io.CipherInputStream(is, cipher); + return new Pair<>(cis, cipher.getOutputSize(size)); + } else { + IvParameterSpec ips = new IvParameterSpec(file.getIv()); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips); + Log.d(Config.LOGTAG, "opening encrypted input stream"); + final int s = Config.REPORT_WRONG_FILESIZE_IN_OTR_JINGLE ? size : (size / 16 + 1) * 16; + return new Pair(new CipherInputStream(is, cipher), s); + } + } catch (InvalidKeyException e) { + return null; + } catch (NoSuchAlgorithmException e) { + return null; + } catch (NoSuchPaddingException e) { + return null; + } catch (InvalidAlgorithmParameterException e) { + return null; + } + } - public static OutputStream createAppendedOutputStream(DownloadableFile file) { - return createOutputStream(file, false, true); - } + public static OutputStream createAppendedOutputStream(DownloadableFile file) { + return createOutputStream(file, false, true); + } - public static OutputStream createOutputStream(DownloadableFile file, boolean gcm) { - return createOutputStream(file, gcm, false); - } + public static OutputStream createOutputStream(DownloadableFile file, boolean gcm) { + return createOutputStream(file, gcm, false); + } - private static OutputStream createOutputStream(DownloadableFile file, boolean gcm, boolean append) { - FileOutputStream os; - try { - os = new FileOutputStream(file, append); - if (file.getKey() == null) { - return os; - } - } catch (FileNotFoundException e) { - return null; - } - try { - if (gcm) { - AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); - cipher.init(false, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv())); - return new org.bouncycastle.crypto.io.CipherOutputStream(os, cipher); - } else { - IvParameterSpec ips = new IvParameterSpec(file.getIv()); - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips); - Log.d(Config.LOGTAG, "opening encrypted output stream"); - return new CipherOutputStream(os, cipher); - } - } catch (InvalidKeyException e) { - return null; - } catch (NoSuchAlgorithmException e) { - return null; - } catch (NoSuchPaddingException e) { - return null; - } catch (InvalidAlgorithmParameterException e) { - return null; - } - } + private static OutputStream createOutputStream(DownloadableFile file, boolean gcm, boolean append) { + FileOutputStream os; + try { + os = new FileOutputStream(file, append); + if (file.getKey() == null) { + return os; + } + } catch (FileNotFoundException e) { + return null; + } + try { + if (gcm) { + AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); + cipher.init(false, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv())); + return new org.bouncycastle.crypto.io.CipherOutputStream(os, cipher); + } else { + IvParameterSpec ips = new IvParameterSpec(file.getIv()); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips); + Log.d(Config.LOGTAG, "opening encrypted output stream"); + return new CipherOutputStream(os, cipher); + } + } catch (InvalidKeyException e) { + return null; + } catch (NoSuchAlgorithmException e) { + return null; + } catch (NoSuchPaddingException e) { + return null; + } catch (InvalidAlgorithmParameterException e) { + return null; + } + } - public PowerManager.WakeLock createWakeLock(String name) { - PowerManager powerManager = (PowerManager) mXmppConnectionService.getSystemService(Context.POWER_SERVICE); - return powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,name); - } + public PowerManager.WakeLock createWakeLock(String name) { + PowerManager powerManager = (PowerManager) mXmppConnectionService.getSystemService(Context.POWER_SERVICE); + return powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name); + } } diff --git a/src/main/java/de/pixart/messenger/services/AlarmReceiver.java b/src/main/java/de/pixart/messenger/services/AlarmReceiver.java index 0fd3ba049..e20b567dd 100644 --- a/src/main/java/de/pixart/messenger/services/AlarmReceiver.java +++ b/src/main/java/de/pixart/messenger/services/AlarmReceiver.java @@ -7,7 +7,7 @@ import android.util.Log; import de.pixart.messenger.Config; -public class AlarmReceiver extends BroadcastReceiver{ +public class AlarmReceiver extends BroadcastReceiver { public static final int SCHEDULE_ALARM_REQUEST_CODE = 523976483; @Override diff --git a/src/main/java/de/pixart/messenger/services/AvatarService.java b/src/main/java/de/pixart/messenger/services/AvatarService.java index 943516271..4e7c69b22 100644 --- a/src/main/java/de/pixart/messenger/services/AvatarService.java +++ b/src/main/java/de/pixart/messenger/services/AvatarService.java @@ -26,406 +26,406 @@ import de.pixart.messenger.xmpp.XmppConnection; public class AvatarService implements OnAdvancedStreamFeaturesLoaded { - private static final int FG_COLOR = 0xFFFAFAFA; - private static final int TRANSPARENT = 0x00000000; - private static final int PLACEHOLDER_COLOR = 0xFF202020; - - private static final String PREFIX_CONTACT = "contact"; - private static final String PREFIX_CONVERSATION = "conversation"; - private static final String PREFIX_ACCOUNT = "account"; - private static final String PREFIX_GENERIC = "generic"; - - final private ArrayList sizes = new ArrayList<>(); - - protected XmppConnectionService mXmppConnectionService = null; - - public AvatarService(XmppConnectionService service) { - this.mXmppConnectionService = service; - } - - private Bitmap get(final Contact contact, final int size, boolean cachedOnly) { - final String KEY = key(contact, size); - Bitmap avatar = this.mXmppConnectionService.getBitmapCache().get(KEY); - if (avatar != null || cachedOnly) { - return avatar; - } - if (avatar == null && contact.getAvatar() != null) { - avatar = mXmppConnectionService.getFileBackend().getAvatar(contact.getAvatar(), size); - } - if (avatar == null && contact.getProfilePhoto() != null) { - avatar = mXmppConnectionService.getFileBackend().cropCenterSquare(Uri.parse(contact.getProfilePhoto()), size); - } - if (avatar == null) { + private static final int FG_COLOR = 0xFFFAFAFA; + private static final int TRANSPARENT = 0x00000000; + private static final int PLACEHOLDER_COLOR = 0xFF202020; + + private static final String PREFIX_CONTACT = "contact"; + private static final String PREFIX_CONVERSATION = "conversation"; + private static final String PREFIX_ACCOUNT = "account"; + private static final String PREFIX_GENERIC = "generic"; + + final private ArrayList sizes = new ArrayList<>(); + + protected XmppConnectionService mXmppConnectionService = null; + + public AvatarService(XmppConnectionService service) { + this.mXmppConnectionService = service; + } + + private Bitmap get(final Contact contact, final int size, boolean cachedOnly) { + final String KEY = key(contact, size); + Bitmap avatar = this.mXmppConnectionService.getBitmapCache().get(KEY); + if (avatar != null || cachedOnly) { + return avatar; + } + if (avatar == null && contact.getAvatar() != null) { + avatar = mXmppConnectionService.getFileBackend().getAvatar(contact.getAvatar(), size); + } + if (avatar == null && contact.getProfilePhoto() != null) { + avatar = mXmppConnectionService.getFileBackend().cropCenterSquare(Uri.parse(contact.getProfilePhoto()), size); + } + if (avatar == null) { avatar = get(contact.getDisplayName(), size, cachedOnly); - } - this.mXmppConnectionService.getBitmapCache().put(KEY, avatar); - return avatar; - } - - public Bitmap get(final MucOptions.User user, final int size, boolean cachedOnly) { - Contact c = user.getContact(); - if (c != null && (c.getProfilePhoto() != null || c.getAvatar() != null || user.getAvatar() == null)) { - return get(c, size, cachedOnly); - } else { - return getImpl(user, size, cachedOnly); - } - } - - private Bitmap getImpl(final MucOptions.User user, final int size, boolean cachedOnly) { - final String KEY = key(user, size); - Bitmap avatar = this.mXmppConnectionService.getBitmapCache().get(KEY); - if (avatar != null || cachedOnly) { - return avatar; - } - if (user.getAvatar() != null) { - avatar = mXmppConnectionService.getFileBackend().getAvatar(user.getAvatar(), size); - } - if (avatar == null) { - Contact contact = user.getContact(); - if (contact != null) { - avatar = get(contact, size, cachedOnly); - } else { - avatar = get(user.getName(), size, cachedOnly); - } - } - this.mXmppConnectionService.getBitmapCache().put(KEY, avatar); - return avatar; - } - - public void clear(Contact contact) { - synchronized (this.sizes) { - for (Integer size : sizes) { - this.mXmppConnectionService.getBitmapCache().remove( - key(contact, size)); - } - } - for(Conversation conversation : mXmppConnectionService.findAllConferencesWith(contact)) { - clear(conversation); - } - } - - private String key(Contact contact, int size) { - synchronized (this.sizes) { - if (!this.sizes.contains(size)) { - this.sizes.add(size); - } - } - return PREFIX_CONTACT + "_" + contact.getAccount().getJid().toBareJid() + "_" - + contact.getJid() + "_" + String.valueOf(size); - } - - private String key(MucOptions.User user, int size) { - synchronized (this.sizes) { - if (!this.sizes.contains(size)) { - this.sizes.add(size); - } - } - return PREFIX_CONTACT + "_" + user.getAccount().getJid().toBareJid() + "_" - + user.getFullJid() + "_" + String.valueOf(size); - } - - public Bitmap get(ListItem item, int size) { - return get(item,size,false); - } - - public Bitmap get(ListItem item, int size, boolean cachedOnly) { - if (item instanceof Contact) { - return get((Contact) item, size,cachedOnly); - } else if (item instanceof Bookmark) { - Bookmark bookmark = (Bookmark) item; - if (bookmark.getConversation() != null) { - return get(bookmark.getConversation(), size, cachedOnly); - } else { - return get(bookmark.getDisplayName(), size, cachedOnly); - } - } else { - return get(item.getDisplayName(), size, cachedOnly); - } - } - - public Bitmap get(Conversation conversation, int size) { - return get(conversation,size,false); - } - - public Bitmap get(Conversation conversation, int size, boolean cachedOnly) { - if (conversation.getMode() == Conversation.MODE_SINGLE) { - return get(conversation.getContact(), size, cachedOnly); - } else { - return get(conversation.getMucOptions(), size, cachedOnly); - } - } - - public void clear(Conversation conversation) { - if (conversation.getMode() == Conversation.MODE_SINGLE) { - clear(conversation.getContact()); - } else { - clear(conversation.getMucOptions()); - } - } - - private Bitmap get(MucOptions mucOptions, int size, boolean cachedOnly) { - final String KEY = key(mucOptions, size); - Bitmap bitmap = this.mXmppConnectionService.getBitmapCache().get(KEY); - if (bitmap != null || cachedOnly) { - return bitmap; - } - final List users = mucOptions.getUsers(); - int count = users.size(); - bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - bitmap.eraseColor(TRANSPARENT); - - if (count == 0) { - String name = mucOptions.getConversation().getName(); - drawTile(canvas, name, 0, 0, size, size); - } else if (count == 1) { - drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size); - drawTile(canvas, mucOptions.getConversation().getAccount(), size / 2 + 1, 0, size, size); - } else if (count == 2) { - drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size); - drawTile(canvas, users.get(1), size / 2 + 1, 0, size, size); - } else if (count == 3) { - drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size); - drawTile(canvas, users.get(1), size / 2 + 1, 0, size, size / 2 - 1); - drawTile(canvas, users.get(2), size / 2 + 1, size / 2 + 1, size, - size); - } else if (count == 4) { - drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size / 2 - 1); - drawTile(canvas, users.get(1), 0, size / 2 + 1, size / 2 - 1, size); - drawTile(canvas, users.get(2), size / 2 + 1, 0, size, size / 2 - 1); - drawTile(canvas, users.get(3), size / 2 + 1, size / 2 + 1, size, - size); - } else { - drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size / 2 - 1); - drawTile(canvas, users.get(1), 0, size / 2 + 1, size / 2 - 1, size); - drawTile(canvas, users.get(2), size / 2 + 1, 0, size, size / 2 - 1); - drawTile(canvas, "\u2026", PLACEHOLDER_COLOR, size / 2 + 1, size / 2 + 1, - size, size); - } - this.mXmppConnectionService.getBitmapCache().put(KEY, bitmap); - return bitmap; - } - - public void clear(MucOptions options) { - synchronized (this.sizes) { - for (Integer size : sizes) { - this.mXmppConnectionService.getBitmapCache().remove(key(options, size)); - } - } - } - - private String key(MucOptions options, int size) { - synchronized (this.sizes) { - if (!this.sizes.contains(size)) { - this.sizes.add(size); - } - } - return PREFIX_CONVERSATION + "_" + options.getConversation().getUuid() - + "_" + String.valueOf(size); - } - - public Bitmap get(Account account, int size) { - return get(account, size, false); - } - - public Bitmap get(Account account, int size, boolean cachedOnly) { - final String KEY = key(account, size); - Bitmap avatar = mXmppConnectionService.getBitmapCache().get(KEY); - if (avatar != null || cachedOnly) { - return avatar; - } - avatar = mXmppConnectionService.getFileBackend().getAvatar(account.getAvatar(), size); - if (avatar == null) { - avatar = get(account.getJid().toBareJid().toString(), size,false); - } - mXmppConnectionService.getBitmapCache().put(KEY, avatar); - return avatar; - } - - public Bitmap get(Message message, int size, boolean cachedOnly) { - final Conversation conversation = message.getConversation(); - if (message.getStatus() == Message.STATUS_RECEIVED) { - Contact c = message.getContact(); - if (c != null && (c.getProfilePhoto() != null || c.getAvatar() != null)) { - return get(c, size, cachedOnly); - } else if (message.getConversation().getMode() == Conversation.MODE_MULTI){ - MucOptions.User user = conversation.getMucOptions().findUserByFullJid(message.getCounterpart()); - if (user != null) { - return getImpl(user,size,cachedOnly); - } - } - return get(UIHelper.getMessageDisplayName(message), size, cachedOnly); - } else { - return get(conversation.getAccount(), size, cachedOnly); - } - } - - public void clear(Account account) { - synchronized (this.sizes) { - for (Integer size : sizes) { - this.mXmppConnectionService.getBitmapCache().remove(key(account, size)); - } - } - } - - public void clear(MucOptions.User user) { - synchronized (this.sizes) { - for (Integer size : sizes) { - this.mXmppConnectionService.getBitmapCache().remove(key(user, size)); - } - } - } - - private String key(Account account, int size) { - synchronized (this.sizes) { - if (!this.sizes.contains(size)) { - this.sizes.add(size); - } - } - return PREFIX_ACCOUNT + "_" + account.getUuid() + "_" - + String.valueOf(size); - } - - public Bitmap get(String name, int size) { - return get(name,size,false); - } - - public Bitmap get(final String name, final int size, boolean cachedOnly) { - final String KEY = key(name, size); - Bitmap bitmap = mXmppConnectionService.getBitmapCache().get(KEY); - if (bitmap != null || cachedOnly) { - return bitmap; - } - bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - final String trimmedName = name == null ? "" : name.trim(); - drawTile(canvas, trimmedName, 0, 0, size, size); - mXmppConnectionService.getBitmapCache().put(KEY, bitmap); - return bitmap; - } - - private String key(String name, int size) { - synchronized (this.sizes) { - if (!this.sizes.contains(size)) { - this.sizes.add(size); - } - } - return PREFIX_GENERIC + "_" + name + "_" + String.valueOf(size); - } - - private boolean drawTile(Canvas canvas, String letter, int tileColor, - int left, int top, int right, int bottom) { - letter = letter.toUpperCase(Locale.getDefault()); - Paint tilePaint = new Paint(), textPaint = new Paint(); - tilePaint.setColor(tileColor); - textPaint.setFlags(Paint.ANTI_ALIAS_FLAG); - textPaint.setColor(FG_COLOR); - textPaint.setTypeface(Typeface.create("sans-serif-light", - Typeface.NORMAL)); - textPaint.setTextSize((float) ((right - left) * 0.8)); - Rect rect = new Rect(); - - canvas.drawRect(new Rect(left, top, right, bottom), tilePaint); - textPaint.getTextBounds(letter, 0, 1, rect); - float width = textPaint.measureText(letter); - canvas.drawText(letter, (right + left) / 2 - width / 2, (top + bottom) - / 2 + rect.height() / 2, textPaint); - return true; - } - - private boolean drawTile(Canvas canvas, MucOptions.User user, int left, - int top, int right, int bottom) { - Contact contact = user.getContact(); - if (contact != null) { - Uri uri = null; - if (contact.getAvatar() != null) { - uri = mXmppConnectionService.getFileBackend().getAvatarUri( - contact.getAvatar()); - } else if (contact.getProfilePhoto() != null) { - uri = Uri.parse(contact.getProfilePhoto()); - } - if (drawTile(canvas, uri, left, top, right, bottom)) { - return true; - } - } else if (user.getAvatar() != null) { - Uri uri = mXmppConnectionService.getFileBackend().getAvatarUri(user.getAvatar()); - if (drawTile(canvas, uri, left, top, right, bottom)) { - return true; - } - } else if (user.getAvatar() != null) { - Uri uri = mXmppConnectionService.getFileBackend().getAvatarUri(user.getAvatar()); - if (drawTile(canvas, uri, left, top, right, bottom)) { - return true; - } - } else if (user.getAvatar() != null) { - Uri uri = mXmppConnectionService.getFileBackend().getAvatarUri(user.getAvatar()); - if (drawTile(canvas, uri, left, top, right, bottom)) { - return true; - } - } - String name = contact != null ? contact.getDisplayName() : user.getName(); - drawTile(canvas, name, left, top, right, bottom); - return true; - } - - private boolean drawTile(Canvas canvas, Account account, int left, int top, int right, int bottom) { - String avatar = account.getAvatar(); - if (avatar != null) { - Uri uri = mXmppConnectionService.getFileBackend().getAvatarUri(avatar); - if (uri != null) { - if (drawTile(canvas, uri, left, top, right, bottom)) { - return true; - } - } - } - return drawTile(canvas, account.getJid().toBareJid().toString(), left, top, right, bottom); - } - - private boolean drawTile(Canvas canvas, String name, int left, int top, int right, int bottom) { - if (name != null) { - final String letter = getFirstLetter(name); - final int color = UIHelper.getColorForName(name); - drawTile(canvas, letter, color, left, top, right, bottom); - return true; - } - return false; - } - - private static String getFirstLetter(String name) { - for(Character c : name.toCharArray()) { - if (Character.isLetterOrDigit(c)) { - return c.toString(); - } - } - return "X"; - } - - private boolean drawTile(Canvas canvas, Uri uri, int left, int top, int right, int bottom) { - if (uri != null) { - Bitmap bitmap = mXmppConnectionService.getFileBackend() - .cropCenter(uri, bottom - top, right - left); - if (bitmap != null) { - drawTile(canvas, bitmap, left, top, right, bottom); - return true; - } - } - return false; - } - - private boolean drawTile(Canvas canvas, Bitmap bm, int dstleft, int dsttop, int dstright, int dstbottom) { - Rect dst = new Rect(dstleft, dsttop, dstright, dstbottom); - canvas.drawBitmap(bm, null, dst, null); - return true; - } - - @Override - public void onAdvancedStreamFeaturesAvailable(Account account) { - XmppConnection.Features features = account.getXmppConnection().getFeatures(); - if (features.pep() && !features.pepPersistent()) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": has pep but is not persistent"); - if (account.getAvatar() != null) { - mXmppConnectionService.republishAvatarIfNeeded(account); - } - } - } + } + this.mXmppConnectionService.getBitmapCache().put(KEY, avatar); + return avatar; + } + + public Bitmap get(final MucOptions.User user, final int size, boolean cachedOnly) { + Contact c = user.getContact(); + if (c != null && (c.getProfilePhoto() != null || c.getAvatar() != null || user.getAvatar() == null)) { + return get(c, size, cachedOnly); + } else { + return getImpl(user, size, cachedOnly); + } + } + + private Bitmap getImpl(final MucOptions.User user, final int size, boolean cachedOnly) { + final String KEY = key(user, size); + Bitmap avatar = this.mXmppConnectionService.getBitmapCache().get(KEY); + if (avatar != null || cachedOnly) { + return avatar; + } + if (user.getAvatar() != null) { + avatar = mXmppConnectionService.getFileBackend().getAvatar(user.getAvatar(), size); + } + if (avatar == null) { + Contact contact = user.getContact(); + if (contact != null) { + avatar = get(contact, size, cachedOnly); + } else { + avatar = get(user.getName(), size, cachedOnly); + } + } + this.mXmppConnectionService.getBitmapCache().put(KEY, avatar); + return avatar; + } + + public void clear(Contact contact) { + synchronized (this.sizes) { + for (Integer size : sizes) { + this.mXmppConnectionService.getBitmapCache().remove( + key(contact, size)); + } + } + for (Conversation conversation : mXmppConnectionService.findAllConferencesWith(contact)) { + clear(conversation); + } + } + + private String key(Contact contact, int size) { + synchronized (this.sizes) { + if (!this.sizes.contains(size)) { + this.sizes.add(size); + } + } + return PREFIX_CONTACT + "_" + contact.getAccount().getJid().toBareJid() + "_" + + contact.getJid() + "_" + String.valueOf(size); + } + + private String key(MucOptions.User user, int size) { + synchronized (this.sizes) { + if (!this.sizes.contains(size)) { + this.sizes.add(size); + } + } + return PREFIX_CONTACT + "_" + user.getAccount().getJid().toBareJid() + "_" + + user.getFullJid() + "_" + String.valueOf(size); + } + + public Bitmap get(ListItem item, int size) { + return get(item, size, false); + } + + public Bitmap get(ListItem item, int size, boolean cachedOnly) { + if (item instanceof Contact) { + return get((Contact) item, size, cachedOnly); + } else if (item instanceof Bookmark) { + Bookmark bookmark = (Bookmark) item; + if (bookmark.getConversation() != null) { + return get(bookmark.getConversation(), size, cachedOnly); + } else { + return get(bookmark.getDisplayName(), size, cachedOnly); + } + } else { + return get(item.getDisplayName(), size, cachedOnly); + } + } + + public Bitmap get(Conversation conversation, int size) { + return get(conversation, size, false); + } + + public Bitmap get(Conversation conversation, int size, boolean cachedOnly) { + if (conversation.getMode() == Conversation.MODE_SINGLE) { + return get(conversation.getContact(), size, cachedOnly); + } else { + return get(conversation.getMucOptions(), size, cachedOnly); + } + } + + public void clear(Conversation conversation) { + if (conversation.getMode() == Conversation.MODE_SINGLE) { + clear(conversation.getContact()); + } else { + clear(conversation.getMucOptions()); + } + } + + private Bitmap get(MucOptions mucOptions, int size, boolean cachedOnly) { + final String KEY = key(mucOptions, size); + Bitmap bitmap = this.mXmppConnectionService.getBitmapCache().get(KEY); + if (bitmap != null || cachedOnly) { + return bitmap; + } + final List users = mucOptions.getUsers(); + int count = users.size(); + bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + bitmap.eraseColor(TRANSPARENT); + + if (count == 0) { + String name = mucOptions.getConversation().getName(); + drawTile(canvas, name, 0, 0, size, size); + } else if (count == 1) { + drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size); + drawTile(canvas, mucOptions.getConversation().getAccount(), size / 2 + 1, 0, size, size); + } else if (count == 2) { + drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size); + drawTile(canvas, users.get(1), size / 2 + 1, 0, size, size); + } else if (count == 3) { + drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size); + drawTile(canvas, users.get(1), size / 2 + 1, 0, size, size / 2 - 1); + drawTile(canvas, users.get(2), size / 2 + 1, size / 2 + 1, size, + size); + } else if (count == 4) { + drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size / 2 - 1); + drawTile(canvas, users.get(1), 0, size / 2 + 1, size / 2 - 1, size); + drawTile(canvas, users.get(2), size / 2 + 1, 0, size, size / 2 - 1); + drawTile(canvas, users.get(3), size / 2 + 1, size / 2 + 1, size, + size); + } else { + drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size / 2 - 1); + drawTile(canvas, users.get(1), 0, size / 2 + 1, size / 2 - 1, size); + drawTile(canvas, users.get(2), size / 2 + 1, 0, size, size / 2 - 1); + drawTile(canvas, "\u2026", PLACEHOLDER_COLOR, size / 2 + 1, size / 2 + 1, + size, size); + } + this.mXmppConnectionService.getBitmapCache().put(KEY, bitmap); + return bitmap; + } + + public void clear(MucOptions options) { + synchronized (this.sizes) { + for (Integer size : sizes) { + this.mXmppConnectionService.getBitmapCache().remove(key(options, size)); + } + } + } + + private String key(MucOptions options, int size) { + synchronized (this.sizes) { + if (!this.sizes.contains(size)) { + this.sizes.add(size); + } + } + return PREFIX_CONVERSATION + "_" + options.getConversation().getUuid() + + "_" + String.valueOf(size); + } + + public Bitmap get(Account account, int size) { + return get(account, size, false); + } + + public Bitmap get(Account account, int size, boolean cachedOnly) { + final String KEY = key(account, size); + Bitmap avatar = mXmppConnectionService.getBitmapCache().get(KEY); + if (avatar != null || cachedOnly) { + return avatar; + } + avatar = mXmppConnectionService.getFileBackend().getAvatar(account.getAvatar(), size); + if (avatar == null) { + avatar = get(account.getJid().toBareJid().toString(), size, false); + } + mXmppConnectionService.getBitmapCache().put(KEY, avatar); + return avatar; + } + + public Bitmap get(Message message, int size, boolean cachedOnly) { + final Conversation conversation = message.getConversation(); + if (message.getStatus() == Message.STATUS_RECEIVED) { + Contact c = message.getContact(); + if (c != null && (c.getProfilePhoto() != null || c.getAvatar() != null)) { + return get(c, size, cachedOnly); + } else if (message.getConversation().getMode() == Conversation.MODE_MULTI) { + MucOptions.User user = conversation.getMucOptions().findUserByFullJid(message.getCounterpart()); + if (user != null) { + return getImpl(user, size, cachedOnly); + } + } + return get(UIHelper.getMessageDisplayName(message), size, cachedOnly); + } else { + return get(conversation.getAccount(), size, cachedOnly); + } + } + + public void clear(Account account) { + synchronized (this.sizes) { + for (Integer size : sizes) { + this.mXmppConnectionService.getBitmapCache().remove(key(account, size)); + } + } + } + + public void clear(MucOptions.User user) { + synchronized (this.sizes) { + for (Integer size : sizes) { + this.mXmppConnectionService.getBitmapCache().remove(key(user, size)); + } + } + } + + private String key(Account account, int size) { + synchronized (this.sizes) { + if (!this.sizes.contains(size)) { + this.sizes.add(size); + } + } + return PREFIX_ACCOUNT + "_" + account.getUuid() + "_" + + String.valueOf(size); + } + + public Bitmap get(String name, int size) { + return get(name, size, false); + } + + public Bitmap get(final String name, final int size, boolean cachedOnly) { + final String KEY = key(name, size); + Bitmap bitmap = mXmppConnectionService.getBitmapCache().get(KEY); + if (bitmap != null || cachedOnly) { + return bitmap; + } + bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + final String trimmedName = name == null ? "" : name.trim(); + drawTile(canvas, trimmedName, 0, 0, size, size); + mXmppConnectionService.getBitmapCache().put(KEY, bitmap); + return bitmap; + } + + private String key(String name, int size) { + synchronized (this.sizes) { + if (!this.sizes.contains(size)) { + this.sizes.add(size); + } + } + return PREFIX_GENERIC + "_" + name + "_" + String.valueOf(size); + } + + private boolean drawTile(Canvas canvas, String letter, int tileColor, + int left, int top, int right, int bottom) { + letter = letter.toUpperCase(Locale.getDefault()); + Paint tilePaint = new Paint(), textPaint = new Paint(); + tilePaint.setColor(tileColor); + textPaint.setFlags(Paint.ANTI_ALIAS_FLAG); + textPaint.setColor(FG_COLOR); + textPaint.setTypeface(Typeface.create("sans-serif-light", + Typeface.NORMAL)); + textPaint.setTextSize((float) ((right - left) * 0.8)); + Rect rect = new Rect(); + + canvas.drawRect(new Rect(left, top, right, bottom), tilePaint); + textPaint.getTextBounds(letter, 0, 1, rect); + float width = textPaint.measureText(letter); + canvas.drawText(letter, (right + left) / 2 - width / 2, (top + bottom) + / 2 + rect.height() / 2, textPaint); + return true; + } + + private boolean drawTile(Canvas canvas, MucOptions.User user, int left, + int top, int right, int bottom) { + Contact contact = user.getContact(); + if (contact != null) { + Uri uri = null; + if (contact.getAvatar() != null) { + uri = mXmppConnectionService.getFileBackend().getAvatarUri( + contact.getAvatar()); + } else if (contact.getProfilePhoto() != null) { + uri = Uri.parse(contact.getProfilePhoto()); + } + if (drawTile(canvas, uri, left, top, right, bottom)) { + return true; + } + } else if (user.getAvatar() != null) { + Uri uri = mXmppConnectionService.getFileBackend().getAvatarUri(user.getAvatar()); + if (drawTile(canvas, uri, left, top, right, bottom)) { + return true; + } + } else if (user.getAvatar() != null) { + Uri uri = mXmppConnectionService.getFileBackend().getAvatarUri(user.getAvatar()); + if (drawTile(canvas, uri, left, top, right, bottom)) { + return true; + } + } else if (user.getAvatar() != null) { + Uri uri = mXmppConnectionService.getFileBackend().getAvatarUri(user.getAvatar()); + if (drawTile(canvas, uri, left, top, right, bottom)) { + return true; + } + } + String name = contact != null ? contact.getDisplayName() : user.getName(); + drawTile(canvas, name, left, top, right, bottom); + return true; + } + + private boolean drawTile(Canvas canvas, Account account, int left, int top, int right, int bottom) { + String avatar = account.getAvatar(); + if (avatar != null) { + Uri uri = mXmppConnectionService.getFileBackend().getAvatarUri(avatar); + if (uri != null) { + if (drawTile(canvas, uri, left, top, right, bottom)) { + return true; + } + } + } + return drawTile(canvas, account.getJid().toBareJid().toString(), left, top, right, bottom); + } + + private boolean drawTile(Canvas canvas, String name, int left, int top, int right, int bottom) { + if (name != null) { + final String letter = getFirstLetter(name); + final int color = UIHelper.getColorForName(name); + drawTile(canvas, letter, color, left, top, right, bottom); + return true; + } + return false; + } + + private static String getFirstLetter(String name) { + for (Character c : name.toCharArray()) { + if (Character.isLetterOrDigit(c)) { + return c.toString(); + } + } + return "X"; + } + + private boolean drawTile(Canvas canvas, Uri uri, int left, int top, int right, int bottom) { + if (uri != null) { + Bitmap bitmap = mXmppConnectionService.getFileBackend() + .cropCenter(uri, bottom - top, right - left); + if (bitmap != null) { + drawTile(canvas, bitmap, left, top, right, bottom); + return true; + } + } + return false; + } + + private boolean drawTile(Canvas canvas, Bitmap bm, int dstleft, int dsttop, int dstright, int dstbottom) { + Rect dst = new Rect(dstleft, dsttop, dstright, dstbottom); + canvas.drawBitmap(bm, null, dst, null); + return true; + } + + @Override + public void onAdvancedStreamFeaturesAvailable(Account account) { + XmppConnection.Features features = account.getXmppConnection().getFeatures(); + if (features.pep() && !features.pepPersistent()) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": has pep but is not persistent"); + if (account.getAvatar() != null) { + mXmppConnectionService.republishAvatarIfNeeded(account); + } + } + } } diff --git a/src/main/java/de/pixart/messenger/services/CheckAppVersionService.java b/src/main/java/de/pixart/messenger/services/CheckAppVersionService.java index 72bc3508a..e3c4353ed 100644 --- a/src/main/java/de/pixart/messenger/services/CheckAppVersionService.java +++ b/src/main/java/de/pixart/messenger/services/CheckAppVersionService.java @@ -19,7 +19,7 @@ public class CheckAppVersionService extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - doPost(request,response); + doPost(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) diff --git a/src/main/java/de/pixart/messenger/services/ContactChooserTargetService.java b/src/main/java/de/pixart/messenger/services/ContactChooserTargetService.java index 8fcedc095..53a7b6625 100644 --- a/src/main/java/de/pixart/messenger/services/ContactChooserTargetService.java +++ b/src/main/java/de/pixart/messenger/services/ContactChooserTargetService.java @@ -22,62 +22,62 @@ import de.pixart.messenger.ui.ShareWithActivity; @TargetApi(Build.VERSION_CODES.M) public class ContactChooserTargetService extends ChooserTargetService implements ServiceConnection { - private final Object lock = new Object(); + private final Object lock = new Object(); - private XmppConnectionService mXmppConnectionService; + private XmppConnectionService mXmppConnectionService; - private final int MAX_TARGETS = 5; + private final int MAX_TARGETS = 5; - @Override - public List onGetChooserTargets(ComponentName targetActivityName, IntentFilter matchedFilter) { - Intent intent = new Intent(this, XmppConnectionService.class); - intent.setAction("contact_chooser"); - startService(intent); - bindService(intent, this, Context.BIND_AUTO_CREATE); - ArrayList chooserTargets = new ArrayList<>(); - try { - waitForService(); - final ArrayList conversations = new ArrayList<>(); - if (!mXmppConnectionService.areMessagesInitialized()) { - return chooserTargets; - } - mXmppConnectionService.populateWithOrderedConversations(conversations, false); - final ComponentName componentName = new ComponentName(this, ShareWithActivity.class); - final int pixel = (int) (48 * getResources().getDisplayMetrics().density); - for(int i = 0; i < Math.min(conversations.size(),MAX_TARGETS); ++i) { - final Conversation conversation = conversations.get(i); - final String name = conversation.getName(); - final Icon icon = Icon.createWithBitmap(mXmppConnectionService.getAvatarService().get(conversation, pixel)); - 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)); - } - } catch (InterruptedException e) { - } - unbindService(this); - return chooserTargets; - } + @Override + public List onGetChooserTargets(ComponentName targetActivityName, IntentFilter matchedFilter) { + Intent intent = new Intent(this, XmppConnectionService.class); + intent.setAction("contact_chooser"); + startService(intent); + bindService(intent, this, Context.BIND_AUTO_CREATE); + ArrayList chooserTargets = new ArrayList<>(); + try { + waitForService(); + final ArrayList conversations = new ArrayList<>(); + if (!mXmppConnectionService.areMessagesInitialized()) { + return chooserTargets; + } + mXmppConnectionService.populateWithOrderedConversations(conversations, false); + final ComponentName componentName = new ComponentName(this, ShareWithActivity.class); + final int pixel = (int) (48 * getResources().getDisplayMetrics().density); + for (int i = 0; i < Math.min(conversations.size(), MAX_TARGETS); ++i) { + final Conversation conversation = conversations.get(i); + final String name = conversation.getName(); + final Icon icon = Icon.createWithBitmap(mXmppConnectionService.getAvatarService().get(conversation, pixel)); + 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)); + } + } catch (InterruptedException e) { + } + unbindService(this); + return chooserTargets; + } - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - XmppConnectionService.XmppConnectionBinder binder = (XmppConnectionService.XmppConnectionBinder) service; - mXmppConnectionService = binder.getService(); - synchronized (this.lock) { - lock.notifyAll(); - } - } + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + XmppConnectionService.XmppConnectionBinder binder = (XmppConnectionService.XmppConnectionBinder) service; + mXmppConnectionService = binder.getService(); + synchronized (this.lock) { + lock.notifyAll(); + } + } - @Override - public void onServiceDisconnected(ComponentName name) { - mXmppConnectionService = null; - } + @Override + public void onServiceDisconnected(ComponentName name) { + mXmppConnectionService = null; + } - private void waitForService() throws InterruptedException { - if (mXmppConnectionService == null) { - synchronized (this.lock) { - lock.wait(); - } - } - } + private void waitForService() throws InterruptedException { + if (mXmppConnectionService == null) { + synchronized (this.lock) { + lock.wait(); + } + } + } } diff --git a/src/main/java/de/pixart/messenger/services/EventReceiver.java b/src/main/java/de/pixart/messenger/services/EventReceiver.java index 63cefe738..cfbbeed48 100644 --- a/src/main/java/de/pixart/messenger/services/EventReceiver.java +++ b/src/main/java/de/pixart/messenger/services/EventReceiver.java @@ -7,19 +7,19 @@ import android.content.Intent; import de.pixart.messenger.persistance.DatabaseBackend; public class EventReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - Intent mIntentForService = new Intent(context, - XmppConnectionService.class); - if (intent.getAction() != null) { - mIntentForService.setAction(intent.getAction()); - } else { - mIntentForService.setAction("other"); - } - final String action = intent.getAction(); - if (action.equals("ui") || DatabaseBackend.getInstance(context).hasEnabledAccounts()) { - context.startService(mIntentForService); - } - } + @Override + public void onReceive(Context context, Intent intent) { + Intent mIntentForService = new Intent(context, + XmppConnectionService.class); + if (intent.getAction() != null) { + mIntentForService.setAction(intent.getAction()); + } else { + mIntentForService.setAction("other"); + } + final String action = intent.getAction(); + if (action.equals("ui") || DatabaseBackend.getInstance(context).hasEnabledAccounts()) { + context.startService(mIntentForService); + } + } } diff --git a/src/main/java/de/pixart/messenger/services/ExportLogsService.java b/src/main/java/de/pixart/messenger/services/ExportLogsService.java index 8dcabcaf7..9008985c6 100644 --- a/src/main/java/de/pixart/messenger/services/ExportLogsService.java +++ b/src/main/java/de/pixart/messenger/services/ExportLogsService.java @@ -37,170 +37,170 @@ import de.pixart.messenger.xmpp.jid.Jid; public class ExportLogsService extends Service { - private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - private static final String DIRECTORY_STRING_FORMAT = FileBackend.getConversationsDirectory() + "/chats/%s"; - private static final String MESSAGE_STRING_FORMAT = "(%s) %s: %s\n"; - private static final int NOTIFICATION_ID = 1; - private static AtomicBoolean running = new AtomicBoolean(false); - private DatabaseBackend mDatabaseBackend; - private List mAccounts; - boolean ReadableLogsEnabled = false; - - @Override - public void onCreate() { - mDatabaseBackend = DatabaseBackend.getInstance(getBaseContext()); - mAccounts = mDatabaseBackend.getAccounts(); - final SharedPreferences ReadableLogs = PreferenceManager.getDefaultSharedPreferences(this); - ReadableLogsEnabled = ReadableLogs.getBoolean("export_plain_text_logs", false); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - if (running.compareAndSet(false, true)) { - new Thread(new Runnable() { - @Override - public void run() { - export(); - stopForeground(true); - running.set(false); - stopSelf(); - } - }).start(); - } - return START_NOT_STICKY; - } - - private void export() { - List conversations = mDatabaseBackend.getConversations(Conversation.STATUS_AVAILABLE); - conversations.addAll(mDatabaseBackend.getConversations(Conversation.STATUS_ARCHIVED)); - NotificationManager mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext()); - mBuilder.setContentTitle(getString(R.string.app_name)) - .setContentText(getString(R.string.notification_export_logs_title)) - .setSmallIcon(R.drawable.ic_import_export_white_24dp) - .setProgress(0, 0, true); - startForeground(NOTIFICATION_ID, mBuilder.build()); + private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + private static final String DIRECTORY_STRING_FORMAT = FileBackend.getConversationsDirectory() + "/chats/%s"; + private static final String MESSAGE_STRING_FORMAT = "(%s) %s: %s\n"; + private static final int NOTIFICATION_ID = 1; + private static AtomicBoolean running = new AtomicBoolean(false); + private DatabaseBackend mDatabaseBackend; + private List mAccounts; + boolean ReadableLogsEnabled = false; + + @Override + public void onCreate() { + mDatabaseBackend = DatabaseBackend.getInstance(getBaseContext()); + mAccounts = mDatabaseBackend.getAccounts(); + final SharedPreferences ReadableLogs = PreferenceManager.getDefaultSharedPreferences(this); + ReadableLogsEnabled = ReadableLogs.getBoolean("export_plain_text_logs", false); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (running.compareAndSet(false, true)) { + new Thread(new Runnable() { + @Override + public void run() { + export(); + stopForeground(true); + running.set(false); + stopSelf(); + } + }).start(); + } + return START_NOT_STICKY; + } + + private void export() { + List conversations = mDatabaseBackend.getConversations(Conversation.STATUS_AVAILABLE); + conversations.addAll(mDatabaseBackend.getConversations(Conversation.STATUS_ARCHIVED)); + NotificationManager mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext()); + mBuilder.setContentTitle(getString(R.string.app_name)) + .setContentText(getString(R.string.notification_export_logs_title)) + .setSmallIcon(R.drawable.ic_import_export_white_24dp) + .setProgress(0, 0, true); + startForeground(NOTIFICATION_ID, mBuilder.build()); mNotifyManager.notify(NOTIFICATION_ID, mBuilder.build()); - if (ReadableLogsEnabled) { - for (Conversation conversation : conversations) { - writeToFile(conversation); - } - } - if (mAccounts.size() == 1) { - try { - ExportDatabase(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - private void writeToFile(Conversation conversation) { - Jid accountJid = resolveAccountUuid(conversation.getAccountUuid()); - Jid contactJid = conversation.getJid(); - - File dir = new File(String.format(DIRECTORY_STRING_FORMAT,accountJid.toBareJid().toString())); - dir.mkdirs(); - - BufferedWriter bw = null; - try { - for (Message message : mDatabaseBackend.getMessagesIterable(conversation)) { - if (message.getType() == Message.TYPE_TEXT || message.hasFileOnRemoteHost()) { - String date = simpleDateFormat.format(new Date(message.getTimeSent())); - if (bw == null) { - bw = new BufferedWriter(new FileWriter( - new File(dir, contactJid.toBareJid().toString() + ".txt"))); - } - String jid = null; - switch (message.getStatus()) { - case Message.STATUS_RECEIVED: - jid = getMessageCounterpart(message); - break; - case Message.STATUS_SEND: - case Message.STATUS_SEND_RECEIVED: - case Message.STATUS_SEND_DISPLAYED: - jid = accountJid.toBareJid().toString(); - break; - } - if (jid != null) { - String body = message.hasFileOnRemoteHost() ? message.getFileParams().url.toString() : message.getBody(); - bw.write(String.format(MESSAGE_STRING_FORMAT, date, jid, - body.replace("\\\n", "\\ \n").replace("\n", "\\ \n"))); - } - } - } - } catch (IOException e) { - e.printStackTrace(); - } finally { - try { - if (bw != null) { - bw.close(); - } - } catch (IOException e1) { - e1.printStackTrace(); - } - } - } - - private Jid resolveAccountUuid(String accountUuid) { - for (Account account : mAccounts) { - if (account.getUuid().equals(accountUuid)) { - return account.getJid(); - } - } - return null; - } - - private String getMessageCounterpart(Message message) { - String trueCounterpart = (String) message.getContentValues().get(Message.TRUE_COUNTERPART); - if (trueCounterpart != null) { - return trueCounterpart; - } else { - return message.getCounterpart().toString(); - } - } - - public void ExportDatabase() throws IOException { - Account mAccount = mAccounts.get(0); - // Get hold of the db: - FileInputStream InputFile = new FileInputStream(this.getDatabasePath(DatabaseBackend.DATABASE_NAME)); - // Set the output folder on the SDcard - File directory = new File(FileBackend.getConversationsDirectory() + "/database/"); - // Create the folder if it doesn't exist: - if (!directory.exists()) { - directory.mkdirs(); - } - //Delete old database export file - File temp_db_file = new File(directory + "/database.bak"); - if (temp_db_file.exists()) { + if (ReadableLogsEnabled) { + for (Conversation conversation : conversations) { + writeToFile(conversation); + } + } + if (mAccounts.size() == 1) { + try { + ExportDatabase(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + private void writeToFile(Conversation conversation) { + Jid accountJid = resolveAccountUuid(conversation.getAccountUuid()); + Jid contactJid = conversation.getJid(); + + File dir = new File(String.format(DIRECTORY_STRING_FORMAT, accountJid.toBareJid().toString())); + dir.mkdirs(); + + BufferedWriter bw = null; + try { + for (Message message : mDatabaseBackend.getMessagesIterable(conversation)) { + if (message.getType() == Message.TYPE_TEXT || message.hasFileOnRemoteHost()) { + String date = simpleDateFormat.format(new Date(message.getTimeSent())); + if (bw == null) { + bw = new BufferedWriter(new FileWriter( + new File(dir, contactJid.toBareJid().toString() + ".txt"))); + } + String jid = null; + switch (message.getStatus()) { + case Message.STATUS_RECEIVED: + jid = getMessageCounterpart(message); + break; + case Message.STATUS_SEND: + case Message.STATUS_SEND_RECEIVED: + case Message.STATUS_SEND_DISPLAYED: + jid = accountJid.toBareJid().toString(); + break; + } + if (jid != null) { + String body = message.hasFileOnRemoteHost() ? message.getFileParams().url.toString() : message.getBody(); + bw.write(String.format(MESSAGE_STRING_FORMAT, date, jid, + body.replace("\\\n", "\\ \n").replace("\n", "\\ \n"))); + } + } + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if (bw != null) { + bw.close(); + } + } catch (IOException e1) { + e1.printStackTrace(); + } + } + } + + private Jid resolveAccountUuid(String accountUuid) { + for (Account account : mAccounts) { + if (account.getUuid().equals(accountUuid)) { + return account.getJid(); + } + } + return null; + } + + private String getMessageCounterpart(Message message) { + String trueCounterpart = (String) message.getContentValues().get(Message.TRUE_COUNTERPART); + if (trueCounterpart != null) { + return trueCounterpart; + } else { + return message.getCounterpart().toString(); + } + } + + public void ExportDatabase() throws IOException { + Account mAccount = mAccounts.get(0); + // Get hold of the db: + FileInputStream InputFile = new FileInputStream(this.getDatabasePath(DatabaseBackend.DATABASE_NAME)); + // Set the output folder on the SDcard + File directory = new File(FileBackend.getConversationsDirectory() + "/database/"); + // Create the folder if it doesn't exist: + if (!directory.exists()) { + directory.mkdirs(); + } + //Delete old database export file + File temp_db_file = new File(directory + "/database.bak"); + if (temp_db_file.exists()) { Log.d(Config.LOGTAG, "Delete temp database backup file from " + temp_db_file.toString()); - temp_db_file.delete(); - } - // Set the output file stream up: - FileOutputStream OutputFile = new FileOutputStream(directory.getPath() + "/database.db.crypt"); - - String EncryptionKey = mAccount.getPassword(); //get account password - - // encrypt database from the input file to the output file - try { - EncryptDecryptFile.encrypt(InputFile, OutputFile, EncryptionKey); - } catch (NoSuchAlgorithmException e) { - Log.d(Config.LOGTAG,"Database exporter: encryption failed with " + e); - e.printStackTrace(); - } catch (NoSuchPaddingException e) { - Log.d(Config.LOGTAG,"Database exporter: encryption failed with " + e); - e.printStackTrace(); - } catch (InvalidKeyException e) { - Log.d(Config.LOGTAG,"Database exporter: encryption failed (invalid key) with " + e); - e.printStackTrace(); - } catch (IOException e) { - Log.d(Config.LOGTAG,"Database exporter: encryption failed (IO) with " + e); - e.printStackTrace(); - } - } - - @Override - public IBinder onBind(Intent intent) { - return null; - } + temp_db_file.delete(); + } + // Set the output file stream up: + FileOutputStream OutputFile = new FileOutputStream(directory.getPath() + "/database.db.crypt"); + + String EncryptionKey = mAccount.getPassword(); //get account password + + // encrypt database from the input file to the output file + try { + EncryptDecryptFile.encrypt(InputFile, OutputFile, EncryptionKey); + } catch (NoSuchAlgorithmException e) { + Log.d(Config.LOGTAG, "Database exporter: encryption failed with " + e); + e.printStackTrace(); + } catch (NoSuchPaddingException e) { + Log.d(Config.LOGTAG, "Database exporter: encryption failed with " + e); + e.printStackTrace(); + } catch (InvalidKeyException e) { + Log.d(Config.LOGTAG, "Database exporter: encryption failed (invalid key) with " + e); + e.printStackTrace(); + } catch (IOException e) { + Log.d(Config.LOGTAG, "Database exporter: encryption failed (IO) with " + e); + e.printStackTrace(); + } + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } } diff --git a/src/main/java/de/pixart/messenger/services/MessageArchiveService.java b/src/main/java/de/pixart/messenger/services/MessageArchiveService.java index 4748dc86d..702e0d093 100644 --- a/src/main/java/de/pixart/messenger/services/MessageArchiveService.java +++ b/src/main/java/de/pixart/messenger/services/MessageArchiveService.java @@ -22,29 +22,29 @@ import de.pixart.messenger.xmpp.stanzas.IqPacket; public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { - private final XmppConnectionService mXmppConnectionService; - - private final HashSet queries = new HashSet<>(); - private final ArrayList pendingQueries = new ArrayList<>(); - - public enum PagingOrder { - NORMAL, - REVERSE - } - - public MessageArchiveService(final XmppConnectionService service) { - this.mXmppConnectionService = service; - } - - private void catchup(final Account account) { - synchronized (this.queries) { - for(Iterator iterator = this.queries.iterator(); iterator.hasNext();) { - Query query = iterator.next(); - if (query.getAccount() == account) { - iterator.remove(); - } - } - } + private final XmppConnectionService mXmppConnectionService; + + private final HashSet queries = new HashSet<>(); + private final ArrayList pendingQueries = new ArrayList<>(); + + public enum PagingOrder { + NORMAL, + REVERSE + } + + public MessageArchiveService(final XmppConnectionService service) { + this.mXmppConnectionService = service; + } + + private void catchup(final Account account) { + synchronized (this.queries) { + for (Iterator iterator = this.queries.iterator(); iterator.hasNext(); ) { + Query query = iterator.next(); + if (query.getAccount() == account) { + iterator.remove(); + } + } + } final Pair lastMessageReceived = mXmppConnectionService.databaseBackend.getLastMessageReceived(account); final Pair lastClearDate = mXmppConnectionService.databaseBackend.getLastClearDate(account); long startCatchup; @@ -57,363 +57,363 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { reference = null; } long endCatchup = account.getXmppConnection().getLastSessionEstablished(); - final Query query; - if (startCatchup == 0) { - return; - } else if (endCatchup - startCatchup >= Config.MAM_MAX_CATCHUP) { - startCatchup = endCatchup - Config.MAM_MAX_CATCHUP; - List conversations = mXmppConnectionService.getConversations(); - for (Conversation conversation : conversations) { - if (conversation.getMode() == Conversation.MODE_SINGLE && conversation.getAccount() == account && startCatchup > conversation.getLastMessageTransmitted()) { - this.query(conversation,startCatchup); - } - } - query = new Query(account, startCatchup, endCatchup); - } else { - query = new Query(account, startCatchup, endCatchup); - query.reference = reference; - } - this.queries.add(query); - this.execute(query); - } - - public void catchupMUC(final Conversation conversation) { - if (conversation.getLastMessageTransmitted() < 0 && conversation.countMessages() == 0) { - query(conversation, - 0, - System.currentTimeMillis()); - } else { - query(conversation, - conversation.getLastMessageTransmitted(), - System.currentTimeMillis()); - } - } - - public Query query(final Conversation conversation) { - if (conversation.getLastMessageTransmitted() < 0 && conversation.countMessages() == 0) { - return query(conversation, - 0, - System.currentTimeMillis()); - } else { - return query(conversation, - conversation.getLastMessageTransmitted(), - conversation.getAccount().getXmppConnection().getLastSessionEstablished()); - } - } - - public Query query(final Conversation conversation, long end) { - return this.query(conversation,conversation.getLastMessageTransmitted(),end); - } - - public Query query(Conversation conversation, long start, long end) { - synchronized (this.queries) { - if (start > end) { - return null; - } - final Query query = new Query(conversation, start, end,PagingOrder.REVERSE); - query.reference = conversation.getFirstMamReference(); - this.queries.add(query); - this.execute(query); - return query; - } - } - - public void executePendingQueries(final Account account) { - List pending = new ArrayList<>(); - synchronized(this.pendingQueries) { - for(Iterator iterator = this.pendingQueries.iterator(); iterator.hasNext();) { - Query query = iterator.next(); - if (query.getAccount() == account) { - pending.add(query); - iterator.remove(); - } - } - } - for(Query query : pending) { - this.execute(query); - } - } - - private void execute(final Query query) { - final Account account= query.getAccount(); - if (account.getStatus() == Account.State.ONLINE) { - Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": running mam query " + query.toString()); - IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query); - this.mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.TIMEOUT) { - synchronized (MessageArchiveService.this.queries) { - MessageArchiveService.this.queries.remove(query); - if (query.hasCallback()) { - query.callback(false); - } - } - } else if (packet.getType() != IqPacket.TYPE.RESULT) { - Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": error executing mam: " + packet.toString()); - finalizeQuery(query, true); - } - } - }); - } else { - synchronized (this.pendingQueries) { - this.pendingQueries.add(query); - } - } - } - - private void finalizeQuery(Query query, boolean done) { - synchronized (this.queries) { - this.queries.remove(query); - } - final Conversation conversation = query.getConversation(); - if (conversation != null) { - conversation.sort(); - conversation.setHasMessagesLeftOnServer(!done); - } else { - for(Conversation tmp : this.mXmppConnectionService.getConversations()) { - if (tmp.getAccount() == query.getAccount()) { - tmp.sort(); - } - } - } - if (query.hasCallback()) { - query.callback(done); - } else { - this.mXmppConnectionService.updateConversationUi(); - } - } - - public boolean queryInProgress(Conversation conversation, XmppConnectionService.OnMoreMessagesLoaded callback) { - synchronized (this.queries) { - for(Query query : queries) { - if (query.conversation == conversation) { - if (!query.hasCallback() && callback != null) { - query.setCallback(callback); - } - return true; - } - } - return false; - } - } - - public boolean queryInProgress(Conversation conversation) { - return queryInProgress(conversation, null); - } - - public void processFin(Element fin, Jid from) { - if (fin == null) { - return; - } - Query query = findQuery(fin.getAttribute("queryid")); - if (query == null || !query.validFrom(from)) { - return; - } - boolean complete = fin.getAttributeAsBoolean("complete"); - Element set = fin.findChild("set","http://jabber.org/protocol/rsm"); - Element last = set == null ? null : set.findChild("last"); - Element first = set == null ? null : set.findChild("first"); - Element relevant = query.getPagingOrder() == PagingOrder.NORMAL ? last : first; - boolean abort = (query.getStart() == 0 && query.getTotalCount() >= Config.PAGE_SIZE) || query.getTotalCount() >= Config.MAM_MAX_MESSAGES; - if (query.getConversation() != null) { - query.getConversation().setFirstMamReference(first == null ? null : first.getContent()); - } - if (complete || relevant == null || abort) { - final boolean done = (complete || query.getMessageCount() == 0) && query.getStart() == 0; - this.finalizeQuery(query, done); - Log.d(Config.LOGTAG,query.getAccount().getJid().toBareJid()+": finished mam after "+query.getTotalCount()+" messages. messages left="+Boolean.toString(!done)); - if (query.getWith() == null && query.getMessageCount() > 0) { - mXmppConnectionService.getNotificationService().finishBacklog(true); - } - } else { - final Query nextQuery; - if (query.getPagingOrder() == PagingOrder.NORMAL) { - nextQuery = query.next(last == null ? null : last.getContent()); - } else { - nextQuery = query.prev(first == null ? null : first.getContent()); - } - this.execute(nextQuery); - this.finalizeQuery(query, false); - synchronized (this.queries) { - this.queries.add(nextQuery); - } - } - } - - public Query findQuery(String id) { - if (id == null) { - return null; - } - synchronized (this.queries) { - for(Query query : this.queries) { - if (query.getQueryId().equals(id)) { - return query; - } - } - return null; - } - } - - @Override - public void onAdvancedStreamFeaturesAvailable(Account account) { - if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().mam()) { - this.catchup(account); - } - } - - public class Query { - private int totalCount = 0; - private int messageCount = 0; - private long start; - private long end; - private String queryId; - private String reference = null; - private Account account; - private Conversation conversation; - private PagingOrder pagingOrder = PagingOrder.NORMAL; - private XmppConnectionService.OnMoreMessagesLoaded callback = null; - - - public Query(Conversation conversation, long start, long end) { - this(conversation.getAccount(), start, end); - this.conversation = conversation; - } - - public Query(Conversation conversation, long start, long end, PagingOrder order) { - this(conversation,start,end); - this.pagingOrder = order; - } - - public Query(Account account, long start, long end) { - this.account = account; - this.start = start; - 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; - query.conversation = conversation; - query.totalCount = totalCount; - query.callback = callback; - return query; - } - - public Query next(String reference) { - Query query = page(reference); - query.pagingOrder = PagingOrder.NORMAL; - return query; - } - - public Query prev(String reference) { - Query query = page(reference); - query.pagingOrder = PagingOrder.REVERSE; - return query; - } - - public String getReference() { - return reference; - } - - public PagingOrder getPagingOrder() { - return this.pagingOrder; - } - - public String getQueryId() { - return queryId; - } - - public Jid getWith() { - return conversation == null ? null : conversation.getJid().toBareJid(); - } - - public boolean muc() { - return conversation != null && conversation.getMode() == Conversation.MODE_MULTI; - } - - public long getStart() { - return start; - } - - public void setCallback(XmppConnectionService.OnMoreMessagesLoaded callback) { - this.callback = callback; - } - - public void callback(boolean done) { - if (this.callback != null) { - this.callback.onMoreMessagesLoaded(messageCount,conversation); - if (done) { - this.callback.informUser(R.string.no_more_history_on_server); - } - } - } - - public long getEnd() { - return end; - } - - public Conversation getConversation() { - return conversation; - } - - public Account getAccount() { - return this.account; - } - - public void incrementMessageCount() { - this.messageCount++; - this.totalCount++; - } - - public int getTotalCount() { - return this.totalCount; - } - - public int getMessageCount() { - return this.messageCount; - } - - public boolean validFrom(Jid from) { - if (muc()) { - return getWith().equals(from); - } else { - return (from == null) || account.getJid().toBareJid().equals(from.toBareJid()); - } - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - if (this.muc()) { - builder.append("to="); - builder.append(this.getWith().toString()); - } else { - builder.append("with="); - if (this.getWith() == null) { - builder.append("*"); - } else { - builder.append(getWith().toString()); - } - } - builder.append(", start="); - builder.append(AbstractGenerator.getTimestamp(this.start)); - builder.append(", end="); - builder.append(AbstractGenerator.getTimestamp(this.end)); - if (this.reference!=null) { - if (this.pagingOrder == PagingOrder.NORMAL) { - builder.append(", after="); - } else { - builder.append(", before="); - } - builder.append(this.reference); - } - return builder.toString(); - } - - public boolean hasCallback() { - return this.callback != null; - } - } + final Query query; + if (startCatchup == 0) { + return; + } else if (endCatchup - startCatchup >= Config.MAM_MAX_CATCHUP) { + startCatchup = endCatchup - Config.MAM_MAX_CATCHUP; + List conversations = mXmppConnectionService.getConversations(); + for (Conversation conversation : conversations) { + if (conversation.getMode() == Conversation.MODE_SINGLE && conversation.getAccount() == account && startCatchup > conversation.getLastMessageTransmitted()) { + this.query(conversation, startCatchup); + } + } + query = new Query(account, startCatchup, endCatchup); + } else { + query = new Query(account, startCatchup, endCatchup); + query.reference = reference; + } + this.queries.add(query); + this.execute(query); + } + + public void catchupMUC(final Conversation conversation) { + if (conversation.getLastMessageTransmitted() < 0 && conversation.countMessages() == 0) { + query(conversation, + 0, + System.currentTimeMillis()); + } else { + query(conversation, + conversation.getLastMessageTransmitted(), + System.currentTimeMillis()); + } + } + + public Query query(final Conversation conversation) { + if (conversation.getLastMessageTransmitted() < 0 && conversation.countMessages() == 0) { + return query(conversation, + 0, + System.currentTimeMillis()); + } else { + return query(conversation, + conversation.getLastMessageTransmitted(), + conversation.getAccount().getXmppConnection().getLastSessionEstablished()); + } + } + + public Query query(final Conversation conversation, long end) { + return this.query(conversation, conversation.getLastMessageTransmitted(), end); + } + + public Query query(Conversation conversation, long start, long end) { + synchronized (this.queries) { + if (start > end) { + return null; + } + final Query query = new Query(conversation, start, end, PagingOrder.REVERSE); + query.reference = conversation.getFirstMamReference(); + this.queries.add(query); + this.execute(query); + return query; + } + } + + public void executePendingQueries(final Account account) { + List pending = new ArrayList<>(); + synchronized (this.pendingQueries) { + for (Iterator iterator = this.pendingQueries.iterator(); iterator.hasNext(); ) { + Query query = iterator.next(); + if (query.getAccount() == account) { + pending.add(query); + iterator.remove(); + } + } + } + for (Query query : pending) { + this.execute(query); + } + } + + private void execute(final Query query) { + final Account account = query.getAccount(); + if (account.getStatus() == Account.State.ONLINE) { + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": running mam query " + query.toString()); + IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query); + this.mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.TIMEOUT) { + synchronized (MessageArchiveService.this.queries) { + MessageArchiveService.this.queries.remove(query); + if (query.hasCallback()) { + query.callback(false); + } + } + } else if (packet.getType() != IqPacket.TYPE.RESULT) { + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": error executing mam: " + packet.toString()); + finalizeQuery(query, true); + } + } + }); + } else { + synchronized (this.pendingQueries) { + this.pendingQueries.add(query); + } + } + } + + private void finalizeQuery(Query query, boolean done) { + synchronized (this.queries) { + this.queries.remove(query); + } + final Conversation conversation = query.getConversation(); + if (conversation != null) { + conversation.sort(); + conversation.setHasMessagesLeftOnServer(!done); + } else { + for (Conversation tmp : this.mXmppConnectionService.getConversations()) { + if (tmp.getAccount() == query.getAccount()) { + tmp.sort(); + } + } + } + if (query.hasCallback()) { + query.callback(done); + } else { + this.mXmppConnectionService.updateConversationUi(); + } + } + + public boolean queryInProgress(Conversation conversation, XmppConnectionService.OnMoreMessagesLoaded callback) { + synchronized (this.queries) { + for (Query query : queries) { + if (query.conversation == conversation) { + if (!query.hasCallback() && callback != null) { + query.setCallback(callback); + } + return true; + } + } + return false; + } + } + + public boolean queryInProgress(Conversation conversation) { + return queryInProgress(conversation, null); + } + + public void processFin(Element fin, Jid from) { + if (fin == null) { + return; + } + Query query = findQuery(fin.getAttribute("queryid")); + if (query == null || !query.validFrom(from)) { + return; + } + boolean complete = fin.getAttributeAsBoolean("complete"); + Element set = fin.findChild("set", "http://jabber.org/protocol/rsm"); + Element last = set == null ? null : set.findChild("last"); + Element first = set == null ? null : set.findChild("first"); + Element relevant = query.getPagingOrder() == PagingOrder.NORMAL ? last : first; + boolean abort = (query.getStart() == 0 && query.getTotalCount() >= Config.PAGE_SIZE) || query.getTotalCount() >= Config.MAM_MAX_MESSAGES; + if (query.getConversation() != null) { + query.getConversation().setFirstMamReference(first == null ? null : first.getContent()); + } + if (complete || relevant == null || abort) { + final boolean done = (complete || query.getMessageCount() == 0) && query.getStart() == 0; + this.finalizeQuery(query, done); + Log.d(Config.LOGTAG, query.getAccount().getJid().toBareJid() + ": finished mam after " + query.getTotalCount() + " messages. messages left=" + Boolean.toString(!done)); + if (query.getWith() == null && query.getMessageCount() > 0) { + mXmppConnectionService.getNotificationService().finishBacklog(true); + } + } else { + final Query nextQuery; + if (query.getPagingOrder() == PagingOrder.NORMAL) { + nextQuery = query.next(last == null ? null : last.getContent()); + } else { + nextQuery = query.prev(first == null ? null : first.getContent()); + } + this.execute(nextQuery); + this.finalizeQuery(query, false); + synchronized (this.queries) { + this.queries.add(nextQuery); + } + } + } + + public Query findQuery(String id) { + if (id == null) { + return null; + } + synchronized (this.queries) { + for (Query query : this.queries) { + if (query.getQueryId().equals(id)) { + return query; + } + } + return null; + } + } + + @Override + public void onAdvancedStreamFeaturesAvailable(Account account) { + if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().mam()) { + this.catchup(account); + } + } + + public class Query { + private int totalCount = 0; + private int messageCount = 0; + private long start; + private long end; + private String queryId; + private String reference = null; + private Account account; + private Conversation conversation; + private PagingOrder pagingOrder = PagingOrder.NORMAL; + private XmppConnectionService.OnMoreMessagesLoaded callback = null; + + + public Query(Conversation conversation, long start, long end) { + this(conversation.getAccount(), start, end); + this.conversation = conversation; + } + + public Query(Conversation conversation, long start, long end, PagingOrder order) { + this(conversation, start, end); + this.pagingOrder = order; + } + + public Query(Account account, long start, long end) { + this.account = account; + this.start = start; + 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; + query.conversation = conversation; + query.totalCount = totalCount; + query.callback = callback; + return query; + } + + public Query next(String reference) { + Query query = page(reference); + query.pagingOrder = PagingOrder.NORMAL; + return query; + } + + public Query prev(String reference) { + Query query = page(reference); + query.pagingOrder = PagingOrder.REVERSE; + return query; + } + + public String getReference() { + return reference; + } + + public PagingOrder getPagingOrder() { + return this.pagingOrder; + } + + public String getQueryId() { + return queryId; + } + + public Jid getWith() { + return conversation == null ? null : conversation.getJid().toBareJid(); + } + + public boolean muc() { + return conversation != null && conversation.getMode() == Conversation.MODE_MULTI; + } + + public long getStart() { + return start; + } + + public void setCallback(XmppConnectionService.OnMoreMessagesLoaded callback) { + this.callback = callback; + } + + public void callback(boolean done) { + if (this.callback != null) { + this.callback.onMoreMessagesLoaded(messageCount, conversation); + if (done) { + this.callback.informUser(R.string.no_more_history_on_server); + } + } + } + + public long getEnd() { + return end; + } + + public Conversation getConversation() { + return conversation; + } + + public Account getAccount() { + return this.account; + } + + public void incrementMessageCount() { + this.messageCount++; + this.totalCount++; + } + + public int getTotalCount() { + return this.totalCount; + } + + public int getMessageCount() { + return this.messageCount; + } + + public boolean validFrom(Jid from) { + if (muc()) { + return getWith().equals(from); + } else { + return (from == null) || account.getJid().toBareJid().equals(from.toBareJid()); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + if (this.muc()) { + builder.append("to="); + builder.append(this.getWith().toString()); + } else { + builder.append("with="); + if (this.getWith() == null) { + builder.append("*"); + } else { + builder.append(getWith().toString()); + } + } + builder.append(", start="); + builder.append(AbstractGenerator.getTimestamp(this.start)); + builder.append(", end="); + builder.append(AbstractGenerator.getTimestamp(this.end)); + if (this.reference != null) { + if (this.pagingOrder == PagingOrder.NORMAL) { + builder.append(", after="); + } else { + builder.append(", before="); + } + builder.append(this.reference); + } + return builder.toString(); + } + + public boolean hasCallback() { + return this.callback != null; + } + } } diff --git a/src/main/java/de/pixart/messenger/services/NotificationService.java b/src/main/java/de/pixart/messenger/services/NotificationService.java index a774f7641..8aa8541e9 100644 --- a/src/main/java/de/pixart/messenger/services/NotificationService.java +++ b/src/main/java/de/pixart/messenger/services/NotificationService.java @@ -383,7 +383,7 @@ public class NotificationService { builder.setStyle(messagingStyle); } else { builder.setStyle(new NotificationCompat.BigTextStyle().bigText(getMergedBodies(messages))); - builder.setContentText(UIHelper.getMessagePreview(mXmppConnectionService, messages.get((messages.size()-1))).first); + builder.setContentText(UIHelper.getMessagePreview(mXmppConnectionService, messages.get((messages.size() - 1))).first); } } @@ -485,8 +485,8 @@ public class NotificationService { final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class); intent.setAction(XmppConnectionService.ACTION_REPLY_TO_CONVERSATION); intent.putExtra("uuid", conversation.getUuid()); - intent.putExtra("dismiss_notification",dismissAfterReply); - int id = conversation.getUuid().hashCode() % (dismissAfterReply ? 402359 : 426583); + intent.putExtra("dismiss_notification", dismissAfterReply); + int id = conversation.getUuid().hashCode() % (dismissAfterReply ? 402359 : 426583); return PendingIntent.getService(mXmppConnectionService, id, intent, 0); } @@ -635,7 +635,7 @@ public class NotificationService { mBuilder.setSmallIcon(R.drawable.ic_warning_white_24dp); mBuilder.setContentIntent(PendingIntent.getActivity(mXmppConnectionService, 145, - new Intent(mXmppConnectionService,ManageAccountActivity.class), + new Intent(mXmppConnectionService, ManageAccountActivity.class), PendingIntent.FLAG_UPDATE_CURRENT)); notificationManager.notify(ERROR_NOTIFICATION_ID, mBuilder.build()); } diff --git a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java index e41943597..96b98fbb3 100644 --- a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java +++ b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java @@ -141,3786 +141,3787 @@ import me.leolin.shortcutbadger.ShortcutBadger; public class XmppConnectionService extends Service { - public static final String ACTION_REPLY_TO_CONVERSATION = "reply_to_conversations"; - public static final String ACTION_CLEAR_NOTIFICATION = "clear_notification"; - public static final String ACTION_DISABLE_FOREGROUND = "disable_foreground"; - public static final String ACTION_TRY_AGAIN = "try_again"; - public static final String ACTION_DISMISS_ERROR_NOTIFICATIONS = "dismiss_error"; - public static final String ACTION_IDLE_PING = "idle_ping"; - private static final String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts"; - public static final String ACTION_GCM_TOKEN_REFRESH = "gcm_token_refresh"; - public static final String ACTION_GCM_MESSAGE_RECEIVED = "gcm_message_received"; - private final SerialSingleThreadExecutor mFileAddingExecutor = new SerialSingleThreadExecutor(); - private final SerialSingleThreadExecutor mDatabaseExecutor = new SerialSingleThreadExecutor(); - private ReplacingSerialSingleThreadExecutor mContactMergerExecutor = new ReplacingSerialSingleThreadExecutor(true); - private final IBinder mBinder = new XmppConnectionBinder(); - private final List conversations = new CopyOnWriteArrayList<>(); - private final IqGenerator mIqGenerator = new IqGenerator(this); - private final List mInProgressAvatarFetches = new ArrayList<>(); - private final HashSet mLowPingTimeoutMode = new HashSet<>(); - private WakeLock wakeLock; - private long mLastActivity = 0; - public static VideoCompressor CompressVideo; - private NotificationManager mNotifyManager; - - public DatabaseBackend databaseBackend; - private ContentObserver contactObserver = new ContentObserver(null) { - @Override - public void onChange(boolean selfChange) { - super.onChange(selfChange); - Intent intent = new Intent(getApplicationContext(), - XmppConnectionService.class); - intent.setAction(ACTION_MERGE_PHONE_CONTACTS); - startService(intent); - } - }; - private FileBackend fileBackend = new FileBackend(this); - private MemorizingTrustManager mMemorizingTrustManager; - private NotificationService mNotificationService = new NotificationService( - this); - private OnMessagePacketReceived mMessageParser = new MessageParser(this); - private OnPresencePacketReceived mPresenceParser = new PresenceParser(this); - private IqParser mIqParser = new IqParser(this); - private OnIqPacketReceived mDefaultIqHandler = new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() != IqPacket.TYPE.RESULT) { - Element error = packet.findChild("error"); - String text = error != null ? error.findChildContent("text") : null; - if (text != null) { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": received iq error - " + text); - } - } - } - }; - private MessageGenerator mMessageGenerator = new MessageGenerator(this); - private PresenceGenerator mPresenceGenerator = new PresenceGenerator(this); - private List accounts; - private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager( - this); - public OnContactStatusChanged onContactStatusChanged = new OnContactStatusChanged() { - - @Override - public void onContactStatusChanged(Contact contact, boolean online) { - Conversation conversation = find(getConversations(), contact); - if (conversation != null) { - if (online) { - conversation.endOtrIfNeeded(); - if (contact.getPresences().size() == 1) { - sendUnsentMessages(conversation); - } - } else { - //check if the resource we are haveing a conversation with is still online - if (conversation.hasValidOtrSession()) { - String otrResource = conversation.getOtrSession().getSessionID().getUserID(); - if (!(Arrays.asList(contact.getPresences().toResourceArray()).contains(otrResource))) { - conversation.endOtrIfNeeded(); - } - } - } - } - } - }; - private HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager( - this); - private AvatarService mAvatarService = new AvatarService(this); - private MessageArchiveService mMessageArchiveService = new MessageArchiveService(this); - private PushManagementService mPushManagementService = new PushManagementService(this); - private OnConversationUpdate mOnConversationUpdate = null; - - - private final ConversationsFileObserver fileObserver = new ConversationsFileObserver( - Environment.getExternalStorageDirectory().getAbsolutePath() - ) { - @Override - public void onEvent(int event, String path) { - markFileDeleted(path); - } - }; - private final OnJinglePacketReceived jingleListener = new OnJinglePacketReceived() { - - @Override - public void onJinglePacketReceived(Account account, JinglePacket packet) { - mJingleConnectionManager.deliverPacket(account, packet); - } - }; - private final OnMessageAcknowledged mOnMessageAcknowledgedListener = new OnMessageAcknowledged() { - - @Override - public void onMessageAcknowledged(Account account, String uuid) { - for (final Conversation conversation : getConversations()) { - if (conversation.getAccount() == account) { - Message message = conversation.findUnsentMessageWithUuid(uuid); - if (message != null) { - markMessage(message, Message.STATUS_SEND); - } - } - } - } - }; - private int convChangedListenerCount = 0; - private OnShowErrorToast mOnShowErrorToast = null; - private int showErrorToastListenerCount = 0; - private int unreadCount = -1; - private OnAccountUpdate mOnAccountUpdate = null; - private OnCaptchaRequested mOnCaptchaRequested = null; - private int accountChangedListenerCount = 0; - private int captchaRequestedListenerCount = 0; - private OnRosterUpdate mOnRosterUpdate = null; - private OnUpdateBlocklist mOnUpdateBlocklist = null; - private int updateBlocklistListenerCount = 0; - private int rosterChangedListenerCount = 0; - private OnMucRosterUpdate mOnMucRosterUpdate = null; - private int mucRosterChangedListenerCount = 0; - private OnKeyStatusUpdated mOnKeyStatusUpdated = null; - private int keyStatusUpdatedListenerCount = 0; - private SecureRandom mRandom; - private LruCache,ServiceDiscoveryResult> discoCache = new LruCache<>(20); - private final OnBindListener mOnBindListener = new OnBindListener() { - - @Override - public void onBind(final Account account) { - synchronized (mInProgressAvatarFetches) { - for (Iterator iterator = mInProgressAvatarFetches.iterator(); iterator.hasNext(); ) { - final String KEY = iterator.next(); - if (KEY.startsWith(account.getJid().toBareJid() + "_")) { - iterator.remove(); - } - } - } - account.getRoster().clearPresences(); - mJingleConnectionManager.cancelInTransmission(); - fetchRosterFromServer(account); - fetchBookmarks(account); - sendPresence(account); - if (mPushManagementService.available(account)) { - mPushManagementService.registerPushTokenOnServer(account); - } - connectMultiModeConversations(account); - syncDirtyContacts(account); - } - }; - private OnStatusChanged statusListener = new OnStatusChanged() { - - @Override - public void onStatusChanged(final Account account) { - XmppConnection connection = account.getXmppConnection(); - if (mOnAccountUpdate != null) { - mOnAccountUpdate.onAccountUpdate(); - } - if (account.getStatus() == Account.State.ONLINE) { - synchronized (mLowPingTimeoutMode) { - if (mLowPingTimeoutMode.remove(account.getJid().toBareJid())) { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": leaving low ping timeout mode"); - } - } - if (account.setShowErrorNotification(true)) { - databaseBackend.updateAccount(account); - } - mMessageArchiveService.executePendingQueries(account); - if (connection != null && connection.getFeatures().csi()) { - if (checkListeners()) { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + " sending csi//inactive"); - connection.sendInactive(); - } else { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + " sending csi//active"); - connection.sendActive(); - } - } - List conversations = getConversations(); - for (Conversation conversation : conversations) { - if (conversation.getAccount() == account - && !account.pendingConferenceJoins.contains(conversation)) { - if (!conversation.startOtrIfNeeded()) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": couldn't start OTR with "+conversation.getContact().getJid()+" when needed"); - } - sendUnsentMessages(conversation); - } - } - for (Conversation conversation : account.pendingConferenceLeaves) { - leaveMuc(conversation); - } - account.pendingConferenceLeaves.clear(); - for (Conversation conversation : account.pendingConferenceJoins) { - joinMuc(conversation); - } - account.pendingConferenceJoins.clear(); - scheduleWakeUpCall(Config.PING_MAX_INTERVAL, account.getUuid().hashCode()); - } else if (account.getStatus() == Account.State.OFFLINE || account.getStatus() == Account.State.DISABLED) { - resetSendingToWaiting(account); - if (!account.isOptionSet(Account.OPTION_DISABLED)) { - synchronized (mLowPingTimeoutMode) { - if (mLowPingTimeoutMode.contains(account.getJid().toBareJid())) { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": went into offline state during low ping mode. reconnecting now"); - reconnectAccount(account, true, false); - } else { - int timeToReconnect = mRandom.nextInt(20) + 10; - scheduleWakeUpCall(timeToReconnect, account.getUuid().hashCode()); - } - } - } - } else if (account.getStatus() == Account.State.REGISTRATION_SUCCESSFUL) { - databaseBackend.updateAccount(account); - reconnectAccount(account, true, false); - } else if ((account.getStatus() != Account.State.CONNECTING) - && (account.getStatus() != Account.State.NO_INTERNET)) { - resetSendingToWaiting(account); - if (connection != null) { - int next = connection.getTimeToNextAttempt(); - Log.d(Config.LOGTAG, account.getJid().toBareJid() - + ": error connecting account. try again in " - + next + "s for the " - + (connection.getAttempt() + 1) + " time"); - scheduleWakeUpCall(next, account.getUuid().hashCode()); - } - } - getNotificationService().updateErrorNotification(); - } - }; - private OpenPgpServiceConnection pgpServiceConnection; - private PgpEngine mPgpEngine = null; - private PowerManager pm; - private LruCache mBitmapCache; - private EventReceiver mEventReceiver = new EventReceiver(); - - private boolean mRestoredFromDatabase = false; - - private static String generateFetchKey(Account account, final Avatar avatar) { - return account.getJid().toBareJid() + "_" + avatar.owner + "_" + avatar.sha1sum; - } - - public boolean areMessagesInitialized() { - return this.mRestoredFromDatabase; - } - - public PgpEngine getPgpEngine() { - if (!Config.supportOpenPgp()) { - return null; - } else if (pgpServiceConnection != null && pgpServiceConnection.isBound()) { - if (this.mPgpEngine == null) { - this.mPgpEngine = new PgpEngine(new OpenPgpApi( - getApplicationContext(), - pgpServiceConnection.getService()), this); - } - return mPgpEngine; - } else { - return null; - } - - } - - public OpenPgpApi getOpenPgpApi() { - if (!Config.supportOpenPgp()) { - return null; - } else if (pgpServiceConnection != null && pgpServiceConnection.isBound()) { - return new OpenPgpApi(this, pgpServiceConnection.getService()); - } else { - return null; - } - } - - public FileBackend getFileBackend() { - return this.fileBackend; - } - - public AvatarService getAvatarService() { - return this.mAvatarService; - } - - public void attachLocationToConversation(final Conversation conversation, - final Uri uri, - final UiCallback callback) { - int encryption = conversation.getNextEncryption(); - if (encryption == Message.ENCRYPTION_PGP) { - encryption = Message.ENCRYPTION_DECRYPTED; - } - Message message = new Message(conversation, uri.toString(), encryption); - if (conversation.getNextCounterpart() != null) { - message.setCounterpart(conversation.getNextCounterpart()); - } - if (encryption == Message.ENCRYPTION_DECRYPTED) { - getPgpEngine().encrypt(message, callback); - } else { - callback.success(message); - } - } - - public void attachFileToConversation(final Conversation conversation, - final Uri uri, - final UiCallback callback) { - if (FileBackend.weOwnFile(this, uri)) { - Log.d(Config.LOGTAG,"trying to attach file that belonged to us"); - callback.error(R.string.security_error_invalid_file_access, null); - return; - } - final Message message; - if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { - message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED); - } else { - message = new Message(conversation, "", conversation.getNextEncryption()); - } - message.setCounterpart(conversation.getNextCounterpart()); - message.setType(Message.TYPE_FILE); - final String path = getFileBackend().getOriginalPath(uri); - Log.d(Config.LOGTAG,"File path = " + path); - mFileAddingExecutor.execute(new Runnable() { - @Override - public void run() { - if (path != null) { - message.setRelativeFilePath(path); - getFileBackend().updateFileParams(message); - if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { - getPgpEngine().encrypt(message, callback); - } else { - callback.success(message); - } - } else { - try { - getFileBackend().copyFileToPrivateStorage(message, uri); - getFileBackend().updateFileParams(message); - if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { - final PgpEngine pgpEngine = getPgpEngine(); - if (pgpEngine != null) { - pgpEngine.encrypt(message, callback); - } else if (callback != null) { - callback.error(R.string.unable_to_connect_to_keychain, null); - } - } else { - callback.success(message); - } - } catch (FileBackend.FileCopyException e) { - callback.error(e.getResId(), message); - } - } - } - }); - } - - public void attachImageToConversation(final Conversation conversation, final Uri uri, final UiCallback callback) { - if (FileBackend.weOwnFile(this, uri)) { - Log.d(Config.LOGTAG,"trying to attach file that belonged to us"); - callback.error(R.string.security_error_invalid_file_access, null); - return; - } - final String compressPictures = getCompressPicturesPreference(); - if ("never".equals(compressPictures) - || ("auto".equals(compressPictures) && getFileBackend().useImageAsIs(uri))) { - Log.d(Config.LOGTAG,conversation.getAccount().getJid().toBareJid()+ ": not compressing picture. sending as file"); - attachFileToConversation(conversation, uri, callback); - return; - } - final Message message; - if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { - message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED); - } else { - message = new Message(conversation, "", conversation.getNextEncryption()); - } - message.setCounterpart(conversation.getNextCounterpart()); - message.setType(Message.TYPE_IMAGE); - mFileAddingExecutor.execute(new Runnable() { - - @Override - public void run() { - try { - getFileBackend().copyImageToPrivateStorage(message, uri); - if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { - final PgpEngine pgpEngine = getPgpEngine(); - if (pgpEngine != null) { - pgpEngine.encrypt(message, callback); - } else if (callback != null){ - callback.error(R.string.unable_to_connect_to_keychain, null); - } - } else { - callback.success(message); - } - } catch (final FileBackend.FileCopyException e) { - callback.error(e.getResId(), message); - } - } - }); - } - - public void attachVideoToConversation(final Conversation conversation, final Uri uri, final UiCallback callback) { - File f = new File(FileUtils.getPath(this, uri)); - long filesize = f.length(); - String path = f.toString(); - final String compressVideos = getCompressVideoPreference(); - boolean sendVideoAsIs = false; - final Integer NOTIFICATION_ID = (int) (new Date().getTime()/1000); - mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext()); - mBuilder.setContentTitle(getString(R.string.app_name)) - .setContentText(getString(R.string.compressing_video)) - .setSmallIcon(R.drawable.ic_play_box_outline_white_24dp) - .setOngoing(true) - .setProgress(0, 0, true); - mNotifyManager.notify(NOTIFICATION_ID, mBuilder.build()); - if (FileBackend.weOwnFile(this, uri)) { - Log.d(Config.LOGTAG,"trying to attach video that belonged to us"); - mNotifyManager.cancel(NOTIFICATION_ID); - callback.error(R.string.security_error_invalid_file_access, null); - return; - } - Log.d(Config.LOGTAG,"Video file (size) :" + f.toString() + "("+filesize/1024/1024+"MB)"); - if (filesize == 0) { - Log.d(Config.LOGTAG,"Error with file, size = 0"); - mNotifyManager.cancel(NOTIFICATION_ID); - callback.error(R.string.error_file_corrupt, null); - return; - } - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US); - File compressed_file = new File(FileBackend.getConversationsVideoDirectory() + "/" - + dateFormat.format(new Date()) - + "_komp.mp4"); - final String compressed_path = compressed_file.toString(); - final Uri compressed_uri = Uri.fromFile(compressed_file); - if ("never".equals(compressVideos)) { - sendVideoAsIs = true; - } else if ("always".equals(compressVideos) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - sendVideoAsIs = false; - } else if ("auto".equals(compressVideos) && filesize > Config.VIDEO_MAX_SIZE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - sendVideoAsIs = false; - } else { - sendVideoAsIs = true; - } - if (!sendVideoAsIs){ - CompressVideo = new VideoCompressor(path, compressed_path, new Interface() { - @Override - public void videocompressed(boolean result) { - if (result) { - Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + ": sending compressed video."); - mNotifyManager.cancel(NOTIFICATION_ID); - attachFileToConversation(conversation, compressed_uri, callback); - } - } - }); - CompressVideo.execute(); - } else { - Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + ": not compressing video. sending as file"); - mNotifyManager.cancel(NOTIFICATION_ID); - attachFileToConversation(conversation, uri, callback); - } - } - - public interface Interface { - void videocompressed(boolean result); - } - - class VideoCompressor extends AsyncTask { - private String originalpath; - private String compressedpath; - private Interface mListener; - - VideoCompressor(String path, String compressed_path, Interface mListener) { - originalpath = path; - compressedpath = compressed_path; - this.mListener = mListener; - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - Log.d(Config.LOGTAG,"Start video compression"); - wakeLock.acquire(); - } - - @Override - protected Boolean doInBackground(String... params) { - return MediaController.getInstance().convertVideo(originalpath, compressedpath); - } - - @Override - protected void onPostExecute(Boolean compressed) { - super.onPostExecute(compressed); - wakeLock.release(); - File video = new File(compressedpath); - if (mListener != null) { - if (video.exists() && video.length() > 0) { - mListener.videocompressed(compressed); - Log.d(Config.LOGTAG, "Compression successfully!"); - } else { - mListener.videocompressed(false); - Log.d(Config.LOGTAG, "Compression failed!"); - } - } - } - } - - public Conversation find(Bookmark bookmark) { - return find(bookmark.getAccount(), bookmark.getJid()); - } - - public Conversation find(final Account account, final Jid jid) { - return find(getConversations(), account, jid); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - final String action = intent == null ? null : intent.getAction(); - String pushedAccountHash = null; - boolean interactive = false; - if (action != null) { - final Conversation c = findConversationByUuid(intent.getStringExtra("uuid")); - switch (action) { - case ConnectivityManager.CONNECTIVITY_ACTION: - if (hasInternetConnection() && Config.RESET_ATTEMPT_COUNT_ON_NETWORK_CHANGE) { - resetAllAttemptCounts(true); - } - break; - case ACTION_MERGE_PHONE_CONTACTS: - if (mRestoredFromDatabase) { - loadPhoneContacts(); - } - return START_STICKY; - case Intent.ACTION_SHUTDOWN: - logoutAndSave(true); - return START_NOT_STICKY; - case ACTION_CLEAR_NOTIFICATION: - if (c != null) { - mNotificationService.clear(c); - } else { - mNotificationService.clear(); - } - break; - case ACTION_DISMISS_ERROR_NOTIFICATIONS: - dismissErrorNotifications(); - break; - case ACTION_TRY_AGAIN: - resetAllAttemptCounts(false); - interactive = true; - break; - case ACTION_REPLY_TO_CONVERSATION: - Bundle remoteInput = RemoteInput.getResultsFromIntent(intent); - if (remoteInput != null && c != null) { - final CharSequence body = remoteInput.getCharSequence("text_reply"); - if (body != null && body.length() > 0) { - directReply(c, body.toString(),intent.getBooleanExtra("dismiss_notification",false)); - } - } - break; - case AudioManager.RINGER_MODE_CHANGED_ACTION: - if (xaOnSilentMode()) { - refreshAllPresences(); - } - break; - case Intent.ACTION_SCREEN_ON: - deactivateGracePeriod(); - case Intent.ACTION_SCREEN_OFF: - if (awayWhenScreenOff()) { - refreshAllPresences(); - } - break; - case ACTION_GCM_TOKEN_REFRESH: - refreshAllGcmTokens(); - break; - case ACTION_IDLE_PING: - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - scheduleNextIdlePing(); - } - break; - case ACTION_GCM_MESSAGE_RECEIVED: - Log.d(Config.LOGTAG,"gcm push message arrived in service. extras="+intent.getExtras()); - pushedAccountHash = intent.getStringExtra("account"); - break; - } - } - this.wakeLock.acquire(); - - boolean pingNow = false; - HashSet pingCandidates = new HashSet<>(); - - for (Account account : accounts) { - if (!account.isOptionSet(Account.OPTION_DISABLED)) { - if (!hasInternetConnection()) { - account.setStatus(Account.State.NO_INTERNET); - if (statusListener != null) { - statusListener.onStatusChanged(account); - } - } else { - if (account.getStatus() == Account.State.NO_INTERNET) { - account.setStatus(Account.State.OFFLINE); - if (statusListener != null) { - statusListener.onStatusChanged(account); - } - } - if (account.getStatus() == Account.State.ONLINE) { - synchronized (mLowPingTimeoutMode) { - long lastReceived = account.getXmppConnection().getLastPacketReceived(); - long lastSent = account.getXmppConnection().getLastPingSent(); - long pingInterval = "ui".equals(action) ? Config.PING_MIN_INTERVAL * 1000 : Config.PING_MAX_INTERVAL * 1000; - long msToNextPing = (Math.max(lastReceived, lastSent) + pingInterval) - SystemClock.elapsedRealtime(); - int pingTimeout = mLowPingTimeoutMode.contains(account.getJid().toBareJid()) ? Config.LOW_PING_TIMEOUT * 1000 : Config.PING_TIMEOUT * 1000; - long pingTimeoutIn = (lastSent + pingTimeout) - SystemClock.elapsedRealtime(); - if (lastSent > lastReceived) { - if (pingTimeoutIn < 0) { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": ping timeout"); - this.reconnectAccount(account, true, interactive); - } else { - int secs = (int) (pingTimeoutIn / 1000); - this.scheduleWakeUpCall(secs, account.getUuid().hashCode()); - } - } else { - pingCandidates.add(account); - if (CryptoHelper.getAccountFingerprint(account).equals(pushedAccountHash)) { - pingNow = true; - if (mLowPingTimeoutMode.add(account.getJid().toBareJid())) { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": entering low ping timeout mode"); - } - } else if (msToNextPing <= 0) { - pingNow = true; - } else { - this.scheduleWakeUpCall((int) (msToNextPing / 1000), account.getUuid().hashCode()); - if (mLowPingTimeoutMode.remove(account.getJid().toBareJid())) { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": leaving low ping timeout mode"); - } - } - } - } - } else if (account.getStatus() == Account.State.OFFLINE) { - reconnectAccount(account, true, interactive); - } else if (account.getStatus() == Account.State.CONNECTING) { - long secondsSinceLastConnect = (SystemClock.elapsedRealtime() - account.getXmppConnection().getLastConnect()) / 1000; - long secondsSinceLastDisco = (SystemClock.elapsedRealtime() - account.getXmppConnection().getLastDiscoStarted()) / 1000; - long discoTimeout = Config.CONNECT_DISCO_TIMEOUT - secondsSinceLastDisco; - long timeout = Config.CONNECT_TIMEOUT - secondsSinceLastConnect; - if (timeout < 0) { - Log.d(Config.LOGTAG, account.getJid() + ": time out during connect reconnecting"); - account.getXmppConnection().resetAttemptCount(); - reconnectAccount(account, true, interactive); - } else if (discoTimeout < 0) { - account.getXmppConnection().sendDiscoTimeout(); - scheduleWakeUpCall((int) Math.min(timeout,discoTimeout), account.getUuid().hashCode()); - } else { - scheduleWakeUpCall((int) Math.min(timeout,discoTimeout), account.getUuid().hashCode()); - } - } else { - if (account.getXmppConnection().getTimeToNextAttempt() <= 0) { - reconnectAccount(account, true, interactive); - } - } - } - if (mOnAccountUpdate != null) { - mOnAccountUpdate.onAccountUpdate(); - } - } - } - if (pingNow) { - for (Account account : pingCandidates) { - synchronized (mLowPingTimeoutMode) { - final boolean lowTimeout = mLowPingTimeoutMode.contains(account.getJid().toBareJid()); - account.getXmppConnection().sendPing(); - Log.d(Config.LOGTAG, account.getJid().toBareJid() + " send ping (action=" + action + ",lowTimeout=" + Boolean.toString(lowTimeout) + ")"); - scheduleWakeUpCall(lowTimeout ? Config.LOW_PING_TIMEOUT : Config.PING_TIMEOUT, account.getUuid().hashCode()); - } - } - } - if (wakeLock.isHeld()) { - try { - wakeLock.release(); - } catch (final RuntimeException ignored) { - } - } - return START_STICKY; - } - - public boolean isDataSaverDisabled() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); - return !connectivityManager.isActiveNetworkMetered() - || connectivityManager.getRestrictBackgroundStatus() == ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED; - } else { - return true; - } - } - - private void directReply(Conversation conversation, String body, final boolean dismissAfterReply) { - Message message = new Message(conversation,body,conversation.getNextEncryption()); - message.markUnread(); - if (message.getEncryption() == Message.ENCRYPTION_PGP) { - getPgpEngine().encrypt(message, new UiCallback() { - @Override - public void success(Message message) { - message.setEncryption(Message.ENCRYPTION_DECRYPTED); - sendMessage(message); - if (dismissAfterReply) { - markRead(message.getConversation(),true); - } else { - mNotificationService.pushFromDirectReply(message); - } - } - - @Override - public void error(int errorCode, Message object) { - - } - - @Override - public void userInputRequried(PendingIntent pi, Message object) { - - } - }); - } else { - sendMessage(message); - if (dismissAfterReply) { - markRead(conversation,true); - } else { - mNotificationService.pushFromDirectReply(message); - } - } - } - - private boolean xaOnSilentMode() { - return getPreferences().getBoolean("xa_on_silent_mode", false); - } - - private boolean manuallyChangePresence() { - return getPreferences().getBoolean("manually_change_presence", false); - } - - private boolean treatVibrateAsSilent() { - return getPreferences().getBoolean("treat_vibrate_as_silent", false); - } - - private boolean awayWhenScreenOff() { - return getPreferences().getBoolean("away_when_screen_off", false); - } - - private String getCompressPicturesPreference() { - return getPreferences().getString("picture_compression", "auto"); - } - - private String getCompressVideoPreference() { - return getPreferences().getString("video_compression", "auto"); - } - - private Presence.Status getTargetPresence() { - if (xaOnSilentMode() && isPhoneSilenced()) { - return Presence.Status.XA; - } else if (awayWhenScreenOff() && !isInteractive()) { - return Presence.Status.AWAY; - } else { - return Presence.Status.ONLINE; - } - } - - @SuppressLint("NewApi") - @SuppressWarnings("deprecation") - public boolean isInteractive() { - final PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); - - final boolean isScreenOn; - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - isScreenOn = pm.isScreenOn(); - } else { - isScreenOn = pm.isInteractive(); - } - return isScreenOn; - } - - private boolean isPhoneSilenced() { - AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); - try { - if (treatVibrateAsSilent()) { - return audioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL; - } else { - return audioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT; - } - } catch (Throwable throwable) { - Log.d(Config.LOGTAG,"platform bug in isPhoneSilenced ("+ throwable.getMessage()+")"); - return false; - } - } - - private void resetAllAttemptCounts(boolean reallyAll) { - Log.d(Config.LOGTAG, "resetting all attempt counts"); - for (Account account : accounts) { - if (account.hasErrorStatus() || reallyAll) { - final XmppConnection connection = account.getXmppConnection(); - if (connection != null) { - connection.resetAttemptCount(); - } - } - if (account.setShowErrorNotification(true)) { - databaseBackend.updateAccount(account); - } - } - mNotificationService.updateErrorNotification(); - } - - private void dismissErrorNotifications() { - for (final Account account : this.accounts) { - if (account.hasErrorStatus()) { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": dismissing error notification"); - if (account.setShowErrorNotification(false)) { - databaseBackend.updateAccount(account); - } - } - } - } - - public boolean hasInternetConnection() { - ConnectivityManager cm = (ConnectivityManager) getApplicationContext() - .getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); - return activeNetwork != null && activeNetwork.isConnected(); - } - - public boolean isWIFI() { - ConnectivityManager cm = (ConnectivityManager) getApplicationContext() - .getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); - if (activeNetwork != null) { // connected to the internet - if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) { - return true; - } - } - return false; - } - - public boolean isMobile() { - ConnectivityManager cm = (ConnectivityManager) getApplicationContext() - .getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); - if (activeNetwork != null) { // connected to the internet - if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) { - if (!activeNetwork.isRoaming()) { - return true; - } - } - } - return false; - } - - public boolean isMobileRoaming() { - ConnectivityManager cm = (ConnectivityManager) getApplicationContext() - .getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); - if (activeNetwork != null) { // connected to the internet - if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) { - if (activeNetwork.isRoaming()) { - return true; - } - } - } - return false; - } - - @SuppressLint("TrulyRandom") - @Override - public void onCreate() { - ExceptionHelper.init(getApplicationContext()); - PRNGFixes.apply(); - this.mRandom = new SecureRandom(); - updateMemorizingTrustmanager(); - final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); - final int cacheSize = maxMemory / 8; - this.mBitmapCache = new LruCache(cacheSize) { - @Override - protected int sizeOf(final String key, final Bitmap bitmap) { - return bitmap.getByteCount() / 1024; - } - }; - - this.databaseBackend = DatabaseBackend.getInstance(getApplicationContext()); - this.accounts = databaseBackend.getAccounts(); - - if (databaseBackend.startTimeCountExceedsThreshold()) { - Log.d(Config.LOGTAG,"number of restarts exceeds threshold."); - } - - restoreFromDatabase(); - - getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, contactObserver); - new Thread(new Runnable() { - @Override - public void run() { - fileObserver.startWatching(); - } - }).start(); - if (Config.supportOpenPgp()) { - this.pgpServiceConnection = new OpenPgpServiceConnection(getApplicationContext(), "org.sufficientlysecure.keychain", new OpenPgpServiceConnection.OnBound() { - @Override - public void onBound(IOpenPgpService2 service) { - for (Account account : accounts) { - final PgpDecryptionService pgp = account.getPgpDecryptionService(); - if(pgp != null) { - pgp.continueDecryption(true); - } - } - } - - @Override - public void onError(Exception e) { - } - }); - this.pgpServiceConnection.bindToService(); - } - - this.pm = (PowerManager) getSystemService(Context.POWER_SERVICE); - this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "XmppConnectionService"); - toggleForegroundService(); - updateUnreadCountBadge(); - toggleScreenEventReceiver(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - scheduleNextIdlePing(); - } - //start export log service every day at given time - ScheduleAutomaticExport(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - registerReceiver(this.mEventReceiver,new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); - } - } - - @Override - public void onTrimMemory(int level) { - super.onTrimMemory(level); - if (level >= TRIM_MEMORY_COMPLETE) { - Log.d(Config.LOGTAG, "clear cache due to low memory"); - getBitmapCache().evictAll(); - } - } - - @Override - public void onDestroy() { - try { - unregisterReceiver(this.mEventReceiver); - } catch (IllegalArgumentException e) { - //ignored - } - fileObserver.stopWatching(); - super.onDestroy(); - // cancel scheduled exporter - CancelAutomaticExport(); - } - - public void toggleScreenEventReceiver() { - if (awayWhenScreenOff() && !manuallyChangePresence()) { - final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON); - filter.addAction(Intent.ACTION_SCREEN_OFF); - registerReceiver(this.mEventReceiver, filter); - } else { - try { - unregisterReceiver(this.mEventReceiver); - } catch (IllegalArgumentException e) { - //ignored - } - } - } - - public void toggleForegroundService() { - if (Config.USE_ALWAYS_FOREGROUND) { - startForeground(NotificationService.FOREGROUND_NOTIFICATION_ID, this.mNotificationService.createForegroundNotification()); - } else { - stopForeground(true); - } - } - - @Override - public void onTaskRemoved(final Intent rootIntent) { - super.onTaskRemoved(rootIntent); - } - - private void logoutAndSave(boolean stop) { - int activeAccounts = 0; - databaseBackend.clearStartTimeCounter(); // regular swipes don't count towards restart counter - for (final Account account : accounts) { - if (account.getStatus() != Account.State.DISABLED) { - activeAccounts++; - } - databaseBackend.writeRoster(account.getRoster()); - if (account.getXmppConnection() != null) { - new Thread(new Runnable() { - @Override - public void run() { - disconnect(account, false); - } - }).start(); - } - } - if (stop || activeAccounts == 0) { - Log.d(Config.LOGTAG, "good bye"); - stopSelf(); - } - } - - public void scheduleWakeUpCall(int seconds, int requestCode) { - final long timeToWake = SystemClock.elapsedRealtime() + (seconds < 0 ? 1 : seconds + 1) * 1000; - AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - Intent intent = new Intent(this, EventReceiver.class); - intent.setAction("ping"); - PendingIntent alarmIntent = PendingIntent.getBroadcast(this, requestCode, intent, 0); - alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, timeToWake, alarmIntent); - } - - @TargetApi(Build.VERSION_CODES.M) - private void scheduleNextIdlePing() { - Log.d(Config.LOGTAG,"schedule next idle ping"); - AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - Intent intent = new Intent(this, EventReceiver.class); - intent.setAction(ACTION_IDLE_PING); - alarmManager.setAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, - SystemClock.elapsedRealtime()+(Config.IDLE_PING_INTERVAL * 1000), - PendingIntent.getBroadcast(this,0,intent,0) - ); - } - - public XmppConnection createConnection(final Account account) { - final SharedPreferences sharedPref = getPreferences(); - String resource; - try { - resource = sharedPref.getString("resource", getString(R.string.default_resource)).toLowerCase(Locale.ENGLISH); - if (resource.trim().isEmpty()) { - throw new Exception(); - } - } catch (Exception e) { - resource = "Pix-Art Messenger"; - } - account.setResource(resource); - final XmppConnection connection = new XmppConnection(account, this); - connection.setOnMessagePacketReceivedListener(this.mMessageParser); - connection.setOnStatusChangedListener(this.statusListener); - connection.setOnPresencePacketReceivedListener(this.mPresenceParser); - connection.setOnUnregisteredIqPacketReceivedListener(this.mIqParser); - connection.setOnJinglePacketReceivedListener(this.jingleListener); - connection.setOnBindListener(this.mOnBindListener); - connection.setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener); - connection.addOnAdvancedStreamFeaturesAvailableListener(this.mMessageArchiveService); - connection.addOnAdvancedStreamFeaturesAvailableListener(this.mAvatarService); - AxolotlService axolotlService = account.getAxolotlService(); - if (axolotlService != null) { - connection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService); - } - return connection; - } - - public void sendChatState(Conversation conversation) { - if (sendChatStates()) { - MessagePacket packet = mMessageGenerator.generateChatState(conversation); - sendMessagePacket(conversation.getAccount(), packet); - } - } - - private void sendFileMessage(final Message message, final boolean delay) { - Log.d(Config.LOGTAG, "send file message"); - final Account account = message.getConversation().getAccount(); - if (account.httpUploadAvailable(fileBackend.getFile(message,false).getSize())) { - mHttpConnectionManager.createNewUploadConnection(message, delay); - } else { - mJingleConnectionManager.createNewConnection(message); - } - } - - public void sendMessage(final Message message) { - sendMessage(message, false, false); - } - - private void sendMessage(final Message message, final boolean resend, final boolean delay) { - final Account account = message.getConversation().getAccount(); - if (account.setShowErrorNotification(true)) { - databaseBackend.updateAccount(account); - mNotificationService.updateErrorNotification(); - } - final Conversation conversation = message.getConversation(); - account.deactivateGracePeriod(); - MessagePacket packet = null; - final boolean addToConversation = (conversation.getMode() != Conversation.MODE_MULTI - || account.getServerIdentity() != XmppConnection.Identity.SLACK) - && !message.edited(); - boolean saveInDb = addToConversation; - message.setStatus(Message.STATUS_WAITING); - - if (!resend && message.getEncryption() != Message.ENCRYPTION_OTR) { - message.getConversation().endOtrIfNeeded(); - message.getConversation().findUnsentMessagesWithEncryption(Message.ENCRYPTION_OTR, - new Conversation.OnMessageFound() { - @Override - public void onMessageFound(Message message) { - markMessage(message, Message.STATUS_SEND_FAILED); - } - }); - } - - if (account.isOnlineAndConnected()) { - switch (message.getEncryption()) { - case Message.ENCRYPTION_NONE: - if (message.needsUploading()) { - if (account.httpUploadAvailable(fileBackend.getFile(message,false).getSize()) - || message.fixCounterpart()) { - this.sendFileMessage(message, delay); - } else { - break; - } - } else { - packet = mMessageGenerator.generateChat(message); - } - break; - case Message.ENCRYPTION_PGP: - case Message.ENCRYPTION_DECRYPTED: - if (message.needsUploading()) { - if (account.httpUploadAvailable(fileBackend.getFile(message,false).getSize()) - || message.fixCounterpart()) { - this.sendFileMessage(message, delay); - } else { - break; - } - } else { - packet = mMessageGenerator.generatePgpChat(message); - } - break; - case Message.ENCRYPTION_OTR: - SessionImpl otrSession = conversation.getOtrSession(); - if (otrSession != null && otrSession.getSessionStatus() == SessionStatus.ENCRYPTED) { - try { - message.setCounterpart(Jid.fromSessionID(otrSession.getSessionID())); - } catch (InvalidJidException e) { - break; - } - if (message.needsUploading()) { - mJingleConnectionManager.createNewConnection(message); - } else { - packet = mMessageGenerator.generateOtrChat(message); - } - } else if (otrSession == null) { - if (message.fixCounterpart()) { - conversation.startOtrSession(message.getCounterpart().getResourcepart(), true); - } else { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not fix counterpart for OTR message to contact "+message.getContact().getJid()); - break; - } - } else { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+" OTR session with "+message.getContact()+" is in wrong state: "+otrSession.getSessionStatus().toString()); - } - break; - case Message.ENCRYPTION_AXOLOTL: - message.setFingerprint(account.getAxolotlService().getOwnFingerprint()); - if (message.needsUploading()) { - if (account.httpUploadAvailable(fileBackend.getFile(message,false).getSize()) - || message.fixCounterpart()) { - this.sendFileMessage(message, delay); - } else { - break; - } - } else { - XmppAxolotlMessage axolotlMessage = account.getAxolotlService().fetchAxolotlMessageFromCache(message); - if (axolotlMessage == null) { - account.getAxolotlService().preparePayloadMessage(message, delay); - } else { - packet = mMessageGenerator.generateAxolotlChat(message, axolotlMessage); - } - } - break; - - } - if (packet != null) { - if (account.getXmppConnection().getFeatures().sm() || (conversation.getMode() == Conversation.MODE_MULTI && message.getCounterpart().isBareJid())) { - message.setStatus(Message.STATUS_UNSEND); - } else { - message.setStatus(Message.STATUS_SEND); - } - } - } else { - switch (message.getEncryption()) { - case Message.ENCRYPTION_DECRYPTED: - if (!message.needsUploading()) { - String pgpBody = message.getEncryptedBody(); - String decryptedBody = message.getBody(); - message.setBody(pgpBody); - message.setEncryption(Message.ENCRYPTION_PGP); - if (message.edited()) { - message.setBody(decryptedBody); - message.setEncryption(Message.ENCRYPTION_DECRYPTED); - databaseBackend.updateMessage(message, message.getEditedId()); - updateConversationUi(); - return; - } else { - databaseBackend.createMessage(message); - saveInDb = false; - message.setBody(decryptedBody); - message.setEncryption(Message.ENCRYPTION_DECRYPTED); - } - } - break; - case Message.ENCRYPTION_OTR: - if (!conversation.hasValidOtrSession() && message.getCounterpart() != null) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": create otr session without starting for "+message.getContact().getJid()); - conversation.startOtrSession(message.getCounterpart().getResourcepart(), false); - } - break; - case Message.ENCRYPTION_AXOLOTL: - message.setFingerprint(account.getAxolotlService().getOwnFingerprint()); - break; - } - } - - if (resend) { - if (packet != null && addToConversation) { - if (account.getXmppConnection().getFeatures().sm() || (conversation.getMode() == Conversation.MODE_MULTI && message.getCounterpart().isBareJid())) { - markMessage(message, Message.STATUS_UNSEND); - } else { - markMessage(message, Message.STATUS_SEND); - } - } - } else { - if (addToConversation) { - conversation.add(message); - } - if (message.getEncryption() == Message.ENCRYPTION_NONE || saveEncryptedMessages()) { - if (saveInDb) { - databaseBackend.createMessage(message); - } else if (message.edited()) { - databaseBackend.updateMessage(message, message.getEditedId()); - } - } - updateConversationUi(); - } - if (packet != null) { - if (delay) { - mMessageGenerator.addDelay(packet, message.getTimeSent()); - } - if (conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) { - if (this.sendChatStates()) { - packet.addChild(ChatState.toElement(conversation.getOutgoingChatState())); - } - } - sendMessagePacket(account, packet); - } - } - - private void sendUnsentMessages(final Conversation conversation) { - conversation.findWaitingMessages(new Conversation.OnMessageFound() { - - @Override - public void onMessageFound(Message message) { - resendMessage(message, true); - } - }); - } - - public void resendMessage(final Message message, final boolean delay) { - sendMessage(message, true, delay); - } - - public void fetchRosterFromServer(final Account account) { - final IqPacket iqPacket = new IqPacket(IqPacket.TYPE.GET); - if (!"".equals(account.getRosterVersion())) { - Log.d(Config.LOGTAG, account.getJid().toBareJid() - + ": fetching roster version " + account.getRosterVersion()); - } else { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": fetching roster"); - } - iqPacket.query(Xmlns.ROSTER).setAttribute("ver", account.getRosterVersion()); - sendIqPacket(account, iqPacket, mIqParser); - } - - public void fetchBookmarks(final Account account) { - final IqPacket iqPacket = new IqPacket(IqPacket.TYPE.GET); - final Element query = iqPacket.query("jabber:iq:private"); - query.addChild("storage", "storage:bookmarks"); - final OnIqPacketReceived callback = new OnIqPacketReceived() { - - @Override - public void onIqPacketReceived(final Account account, final IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - final Element query = packet.query(); - final HashMap 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")) { - final Bookmark bookmark = Bookmark.parse(item, account); - Bookmark old = bookmarks.put(bookmark.getJid(), bookmark); - if (old != null && old.getBookmarkName() != null && bookmark.getBookmarkName() == null) { - bookmark.setBookmarkName(old.getBookmarkName()); - } - Conversation conversation = find(bookmark); - if (conversation != null) { - conversation.setBookmark(bookmark); - } else if (bookmark.autojoin() && bookmark.getJid() != null && autojoin) { - conversation = findOrCreateConversation( - account, bookmark.getJid(), true); - conversation.setBookmark(bookmark); - joinMuc(conversation); - } - } - } - } - account.setBookmarks(new ArrayList<>(bookmarks.values())); - } else { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not fetch bookmarks"); - } - } - }; - sendIqPacket(account, iqPacket, callback); - } - - public void pushBookmarks(Account account) { - Log.d(Config.LOGTAG, account.getJid().toBareJid()+": pushing bookmarks"); - IqPacket iqPacket = new IqPacket(IqPacket.TYPE.SET); - Element query = iqPacket.query("jabber:iq:private"); - Element storage = query.addChild("storage", "storage:bookmarks"); - for (Bookmark bookmark : account.getBookmarks()) { - storage.addChild(bookmark); - } - sendIqPacket(account, iqPacket, mDefaultIqHandler); - } - - private void restoreFromDatabase() { - synchronized (this.conversations) { - final Map accountLookupTable = new Hashtable<>(); - for (Account account : this.accounts) { - accountLookupTable.put(account.getUuid(), account); - } - this.conversations.addAll(databaseBackend.getConversations(Conversation.STATUS_AVAILABLE)); - for (Conversation conversation : this.conversations) { - Account account = accountLookupTable.get(conversation.getAccountUuid()); - conversation.setAccount(account); - } - Runnable runnable = new Runnable() { - @Override - public void run() { - Log.d(Config.LOGTAG, "restoring roster"); - for (Account account : accounts) { - databaseBackend.readRoster(account.getRoster()); - account.initAccountServices(XmppConnectionService.this); //roster needs to be loaded at this stage - } - getBitmapCache().evictAll(); - loadPhoneContacts(); - Log.d(Config.LOGTAG, "restoring messages"); - for (Conversation conversation : conversations) { - conversation.addAll(0, databaseBackend.getMessages(conversation, Config.PAGE_SIZE)); - checkDeletedFiles(conversation); - conversation.findUnsentTextMessages(new Conversation.OnMessageFound() { - - @Override - public void onMessageFound(Message message) { - markMessage(message, Message.STATUS_WAITING); - } - }); - conversation.findUnreadMessages(new Conversation.OnMessageFound() { - @Override - public void onMessageFound(Message message) { - mNotificationService.pushFromBacklog(message); - } - }); - } - mNotificationService.finishBacklog(false); - mRestoredFromDatabase = true; - Log.d(Config.LOGTAG, "restored all messages"); - updateConversationUi(); - } - }; - mDatabaseExecutor.execute(runnable); - } - } - - public void loadPhoneContacts() { - mContactMergerExecutor.execute(new Runnable() { - @Override - public void run() { - PhoneHelper.loadPhoneContacts(XmppConnectionService.this, new OnPhoneContactsLoadedListener() { - @Override - public void onPhoneContactsLoaded(List phoneContacts) { - Log.d(Config.LOGTAG, "start merging phone contacts with roster"); - for (Account account : accounts) { - List withSystemAccounts = account.getRoster().getWithSystemAccounts(); - for (Bundle phoneContact : phoneContacts) { - Jid jid; - try { - jid = Jid.fromString(phoneContact.getString("jid")); - } catch (final InvalidJidException e) { - continue; - } - final Contact contact = account.getRoster().getContact(jid); - String systemAccount = phoneContact.getInt("phoneid") - + "#" - + phoneContact.getString("lookup"); - contact.setSystemAccount(systemAccount); - if (contact.setPhotoUri(phoneContact.getString("photouri"))) { - getAvatarService().clear(contact); - } - contact.setSystemName(phoneContact.getString("displayname")); - withSystemAccounts.remove(contact); - } - for (Contact contact : withSystemAccounts) { - contact.setSystemAccount(null); - contact.setSystemName(null); - if (contact.setPhotoUri(null)) { - getAvatarService().clear(contact); - } - } - } - Log.d(Config.LOGTAG, "finished merging phone contacts"); - updateAccountUi(); - } - }); - } - }); - } - - public List getConversations() { - return this.conversations; - } - - private void checkDeletedFiles(Conversation conversation) { - conversation.findMessagesWithFiles(new Conversation.OnMessageFound() { - - @Override - public void onMessageFound(Message message) { - if (!getFileBackend().isFileAvailable(message)) { - message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); - final int s = message.getStatus(); - if (s == Message.STATUS_WAITING || s == Message.STATUS_OFFERED || s == Message.STATUS_UNSEND) { - markMessage(message, Message.STATUS_SEND_FAILED); - } - } - } - }); - } - - private void markFileDeleted(final String path) { - Log.d(Config.LOGTAG,"deleted file "+path); - for (Conversation conversation : getConversations()) { - conversation.findMessagesWithFiles(new Conversation.OnMessageFound() { - @Override - public void onMessageFound(Message message) { - DownloadableFile file = fileBackend.getFile(message); - if (file.getAbsolutePath().equals(path)) { - if (!file.exists()) { - message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); - final int s = message.getStatus(); - if (s == Message.STATUS_WAITING || s == Message.STATUS_OFFERED || s == Message.STATUS_UNSEND) { - markMessage(message, Message.STATUS_SEND_FAILED); - } else { - updateConversationUi(); - } - } else { - Log.d(Config.LOGTAG,"found matching message for file "+path+" but file still exists"); - } - } - } - }); - } - } - - public void populateWithOrderedConversations(final List list) { - populateWithOrderedConversations(list, true); - } - - public void populateWithOrderedConversations(final List list, boolean includeNoFileUpload) { - list.clear(); - if (includeNoFileUpload) { - list.addAll(getConversations()); - } else { - for (Conversation conversation : getConversations()) { - if (conversation.getMode() == Conversation.MODE_SINGLE - || conversation.getAccount().httpUploadAvailable()) { - list.add(conversation); - } - } - } - try { - Collections.sort(list); - } catch (IllegalArgumentException e) { - //ignore - } - } - - public void loadMoreMessages(final Conversation conversation, final long timestamp, final OnMoreMessagesLoaded callback) { - if (XmppConnectionService.this.getMessageArchiveService().queryInProgress(conversation, callback)) { - return; - } else if (timestamp == 0) { - return; - } - Log.d(Config.LOGTAG, "load more messages for " + conversation.getName() + " prior to " + MessageGenerator.getTimestamp(timestamp)); - Runnable runnable = new Runnable() { - @Override - public void run() { - final Account account = conversation.getAccount(); - List messages = databaseBackend.getMessages(conversation, 50, timestamp); - if (messages.size() > 0) { - conversation.addAll(0, messages); - checkDeletedFiles(conversation); - callback.onMoreMessagesLoaded(messages.size(), conversation); - } else if (conversation.hasMessagesLeftOnServer() - && account.isOnlineAndConnected() - && conversation.getLastClearHistory() == 0) { - if ((conversation.getMode() == Conversation.MODE_SINGLE && account.getXmppConnection().getFeatures().mam()) - || (conversation.getMode() == Conversation.MODE_MULTI && conversation.getMucOptions().mamSupport())) { - MessageArchiveService.Query query = getMessageArchiveService().query(conversation, 0, timestamp); - if (query != null) { - query.setCallback(callback); - } - callback.informUser(R.string.fetching_history_from_server); - } - } - } - }; - mDatabaseExecutor.execute(runnable); - } - - public List getAccounts() { - return this.accounts; - } - - public List findAllConferencesWith(Contact contact) { - ArrayList results = new ArrayList<>(); - for(Conversation conversation : conversations) { - if (conversation.getMode() == Conversation.MODE_MULTI - && conversation.getMucOptions().isContactInRoom(contact)) { - results.add(conversation); - } - } - return results; - } - - public Conversation find(final Iterable haystack, final Contact contact) { - for (final Conversation conversation : haystack) { - if (conversation.getContact() == contact) { - return conversation; - } - } - return null; - } - - public Conversation find(final Iterable haystack, final Account account, final Jid jid) { - if (jid == null) { - return null; - } - for (final Conversation conversation : haystack) { - if ((account == null || conversation.getAccount() == account) - && (conversation.getJid().toBareJid().equals(jid.toBareJid()))) { - return conversation; - } - } - return null; - } - - public Conversation findOrCreateConversation(final Account account, final Jid jid, final boolean muc) { - return this.findOrCreateConversation(account, jid, muc, null); - } - - public Conversation findOrCreateConversation(final Account account, final Jid jid, final boolean muc, final MessageArchiveService.Query query) { - synchronized (this.conversations) { - Conversation conversation = find(account, jid); - if (conversation != null) { - return conversation; - } - conversation = databaseBackend.findConversation(account, jid); - if (conversation != null) { - conversation.setStatus(Conversation.STATUS_AVAILABLE); - conversation.setAccount(account); - if (muc) { - conversation.setMode(Conversation.MODE_MULTI); - conversation.setContactJid(jid); - } else { - conversation.setMode(Conversation.MODE_SINGLE); - conversation.setContactJid(jid.toBareJid()); - } - conversation.addAll(0, databaseBackend.getMessages(conversation, Config.PAGE_SIZE)); - this.databaseBackend.updateConversation(conversation); - } else { - String conversationName; - Contact contact = account.getRoster().getContact(jid); - if (contact != null) { - conversationName = contact.getDisplayName(); - } else { - conversationName = jid.getLocalpart(); - } - if (muc) { - conversation = new Conversation(conversationName, account, jid, - Conversation.MODE_MULTI); - } else { - conversation = new Conversation(conversationName, account, jid.toBareJid(), - Conversation.MODE_SINGLE); - } - this.databaseBackend.createConversation(conversation); - } - if (account.getXmppConnection() != null - && account.getXmppConnection().getFeatures().mam() - && !muc) { - if (query == null) { - this.mMessageArchiveService.query(conversation); - } else { - if (query.getConversation() == null) { - this.mMessageArchiveService.query(conversation, query.getStart()); - } - } - } - checkDeletedFiles(conversation); - this.conversations.add(conversation); - updateConversationUi(); - return conversation; - } - } - - public void archiveConversation(Conversation conversation) { - getNotificationService().clear(conversation); - conversation.setStatus(Conversation.STATUS_ARCHIVED); - synchronized (this.conversations) { - if (conversation.getMode() == Conversation.MODE_MULTI) { - if (conversation.getAccount().getStatus() == Account.State.ONLINE) { - Bookmark bookmark = conversation.getBookmark(); - if (bookmark != null && bookmark.autojoin() && respectAutojoin()) { - bookmark.setAutojoin(false); - pushBookmarks(bookmark.getAccount()); - } - } - leaveMuc(conversation); - } else { - conversation.endOtrIfNeeded(); - if (conversation.getContact().getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { - Log.d(Config.LOGTAG, "Canceling presence request from " + conversation.getJid().toString()); - sendPresencePacket( - conversation.getAccount(), - mPresenceGenerator.stopPresenceUpdatesTo(conversation.getContact()) - ); - } - } - updateConversation(conversation); - this.conversations.remove(conversation); - updateConversationUi(); - } - } - - public void createAccount(final Account account) { - account.initAccountServices(this); - databaseBackend.createAccount(account); - this.accounts.add(account); - this.reconnectAccountInBackground(account); - updateAccountUi(); - } - - public void createAccountFromKey(final String alias, final OnAccountCreated callback) { - new Thread(new Runnable() { - @Override - public void run() { - try { - X509Certificate[] chain = KeyChain.getCertificateChain(XmppConnectionService.this, alias); - Pair info = CryptoHelper.extractJidAndName(chain[0]); - if (findAccountByJid(info.first) == null) { - Account account = new Account(info.first, ""); - account.setPrivateKeyAlias(alias); - account.setOption(Account.OPTION_DISABLED, true); - account.setDisplayName(info.second); - createAccount(account); - callback.onAccountCreated(account); - if (Config.X509_VERIFICATION) { - try { - getMemorizingTrustManager().getNonInteractive().checkClientTrusted(chain, "RSA"); - } catch (CertificateException e) { - callback.informUser(R.string.certificate_chain_is_not_trusted); - } - } - } else { - callback.informUser(R.string.account_already_exists); - } - } catch (Exception e) { - e.printStackTrace(); - callback.informUser(R.string.unable_to_parse_certificate); - } - } - }).start(); - - } - - public void updateKeyInAccount(final Account account, final String alias) { - Log.d(Config.LOGTAG, "update key in account " + alias); - try { - X509Certificate[] chain = KeyChain.getCertificateChain(XmppConnectionService.this, alias); - Pair info = CryptoHelper.extractJidAndName(chain[0]); - if (account.getJid().toBareJid().equals(info.first)) { - account.setPrivateKeyAlias(alias); - account.setDisplayName(info.second); - databaseBackend.updateAccount(account); - if (Config.X509_VERIFICATION) { - try { - getMemorizingTrustManager().getNonInteractive().checkClientTrusted(chain, "RSA"); - } catch (CertificateException e) { - showErrorToastInUi(R.string.certificate_chain_is_not_trusted); - } - account.getAxolotlService().regenerateKeys(true); - } - } else { - showErrorToastInUi(R.string.jid_does_not_match_certificate); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - public boolean updateAccount(final Account account) { - if (databaseBackend.updateAccount(account)) { - account.setShowErrorNotification(true); - this.statusListener.onStatusChanged(account); - databaseBackend.updateAccount(account); - reconnectAccountInBackground(account); - updateAccountUi(); - getNotificationService().updateErrorNotification(); - return true; - } else { - return false; - } - } - - public void updateAccountPasswordOnServer(final Account account, final String newPassword, final OnAccountPasswordChanged callback) { - final IqPacket iq = getIqGenerator().generateSetPassword(account, newPassword); - sendIqPacket(account, iq, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(final Account account, final IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - account.setPassword(newPassword); - account.setOption(Account.OPTION_MAGIC_CREATE, false); - databaseBackend.updateAccount(account); - callback.onPasswordChangeSucceeded(); - } else { - callback.onPasswordChangeFailed(); - } - } - }); - } - - public void deleteAccount(final Account account) { - synchronized (this.conversations) { - for (final Conversation conversation : conversations) { - if (conversation.getAccount() == account) { - if (conversation.getMode() == Conversation.MODE_MULTI) { - leaveMuc(conversation); - } else if (conversation.getMode() == Conversation.MODE_SINGLE) { - conversation.endOtrIfNeeded(); - } - conversations.remove(conversation); - } - } - if (account.getXmppConnection() != null) { - new Thread(new Runnable() { - @Override - public void run() { - disconnect(account, true); - } - }).start(); - } - Runnable runnable = new Runnable() { - @Override - public void run() { - if (!databaseBackend.deleteAccount(account)) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": unable to delete account"); - } - } - }; - mDatabaseExecutor.execute(runnable); - this.accounts.remove(account); - updateAccountUi(); - getNotificationService().updateErrorNotification(); - } - } - - public void setOnConversationListChangedListener(OnConversationUpdate listener) { - synchronized (this) { - this.mLastActivity = System.currentTimeMillis(); - if (checkListeners()) { - switchToForeground(); - } - this.mOnConversationUpdate = listener; - this.mNotificationService.setIsInForeground(true); - if (this.convChangedListenerCount < 2) { - this.convChangedListenerCount++; - } - } - } - - public void removeOnConversationListChangedListener() { - synchronized (this) { - this.convChangedListenerCount--; - if (this.convChangedListenerCount <= 0) { - this.convChangedListenerCount = 0; - this.mOnConversationUpdate = null; - this.mNotificationService.setIsInForeground(false); - if (checkListeners()) { - switchToBackground(); - } - } - } - } - - public void setOnShowErrorToastListener(OnShowErrorToast onShowErrorToast) { - synchronized (this) { - if (checkListeners()) { - switchToForeground(); - } - this.mOnShowErrorToast = onShowErrorToast; - if (this.showErrorToastListenerCount < 2) { - this.showErrorToastListenerCount++; - } - } - this.mOnShowErrorToast = onShowErrorToast; - } - - public void removeOnShowErrorToastListener() { - synchronized (this) { - this.showErrorToastListenerCount--; - if (this.showErrorToastListenerCount <= 0) { - this.showErrorToastListenerCount = 0; - this.mOnShowErrorToast = null; - if (checkListeners()) { - switchToBackground(); - } - } - } - } - - public void setOnAccountListChangedListener(OnAccountUpdate listener) { - synchronized (this) { - if (checkListeners()) { - switchToForeground(); - } - this.mOnAccountUpdate = listener; - if (this.accountChangedListenerCount < 2) { - this.accountChangedListenerCount++; - } - } - } - - public void removeOnAccountListChangedListener() { - synchronized (this) { - this.accountChangedListenerCount--; - if (this.accountChangedListenerCount <= 0) { - this.mOnAccountUpdate = null; - this.accountChangedListenerCount = 0; - if (checkListeners()) { - switchToBackground(); - } - } - } - } - - public void setOnCaptchaRequestedListener(OnCaptchaRequested listener) { - synchronized (this) { - if (checkListeners()) { - switchToForeground(); - } - this.mOnCaptchaRequested = listener; - if (this.captchaRequestedListenerCount < 2) { - this.captchaRequestedListenerCount++; - } - } - } - - public void removeOnCaptchaRequestedListener() { - synchronized (this) { - this.captchaRequestedListenerCount--; - if (this.captchaRequestedListenerCount <= 0) { - this.mOnCaptchaRequested = null; - this.captchaRequestedListenerCount = 0; - if (checkListeners()) { - switchToBackground(); - } - } - } - } - - public void setOnRosterUpdateListener(final OnRosterUpdate listener) { - synchronized (this) { - if (checkListeners()) { - switchToForeground(); - } - this.mOnRosterUpdate = listener; - if (this.rosterChangedListenerCount < 2) { - this.rosterChangedListenerCount++; - } - } - } - - public void removeOnRosterUpdateListener() { - synchronized (this) { - this.rosterChangedListenerCount--; - if (this.rosterChangedListenerCount <= 0) { - this.rosterChangedListenerCount = 0; - this.mOnRosterUpdate = null; - if (checkListeners()) { - switchToBackground(); - } - } - } - } - - public void setOnUpdateBlocklistListener(final OnUpdateBlocklist listener) { - synchronized (this) { - if (checkListeners()) { - switchToForeground(); - } - this.mOnUpdateBlocklist = listener; - if (this.updateBlocklistListenerCount < 2) { - this.updateBlocklistListenerCount++; - } - } - } - - public void removeOnUpdateBlocklistListener() { - synchronized (this) { - this.updateBlocklistListenerCount--; - if (this.updateBlocklistListenerCount <= 0) { - this.updateBlocklistListenerCount = 0; - this.mOnUpdateBlocklist = null; - if (checkListeners()) { - switchToBackground(); - } - } - } - } - - public void setOnKeyStatusUpdatedListener(final OnKeyStatusUpdated listener) { - synchronized (this) { - if (checkListeners()) { - switchToForeground(); - } - this.mOnKeyStatusUpdated = listener; - if (this.keyStatusUpdatedListenerCount < 2) { - this.keyStatusUpdatedListenerCount++; - } - } - } - - public void removeOnNewKeysAvailableListener() { - synchronized (this) { - this.keyStatusUpdatedListenerCount--; - if (this.keyStatusUpdatedListenerCount <= 0) { - this.keyStatusUpdatedListenerCount = 0; - this.mOnKeyStatusUpdated = null; - if (checkListeners()) { - switchToBackground(); - } - } - } - } - - public void setOnMucRosterUpdateListener(OnMucRosterUpdate listener) { - synchronized (this) { - if (checkListeners()) { - switchToForeground(); - } - this.mOnMucRosterUpdate = listener; - if (this.mucRosterChangedListenerCount < 2) { - this.mucRosterChangedListenerCount++; - } - } - } - - public void removeOnMucRosterUpdateListener() { - synchronized (this) { - this.mucRosterChangedListenerCount--; - if (this.mucRosterChangedListenerCount <= 0) { - this.mucRosterChangedListenerCount = 0; - this.mOnMucRosterUpdate = null; - if (checkListeners()) { - switchToBackground(); - } - } - } - } - - public boolean checkListeners() { - return (this.mOnAccountUpdate == null - && this.mOnConversationUpdate == null - && this.mOnRosterUpdate == null - && this.mOnCaptchaRequested == null - && this.mOnUpdateBlocklist == null - && this.mOnShowErrorToast == null - && this.mOnKeyStatusUpdated == null); - } - - private void switchToForeground() { - final boolean broadcastLastActivity = broadcastLastActivity(); - for (Conversation conversation : getConversations()) { - conversation.setIncomingChatState(ChatState.ACTIVE); - } - for (Account account : getAccounts()) { - if (account.getStatus() == Account.State.ONLINE) { - account.deactivateGracePeriod(); - final XmppConnection connection = account.getXmppConnection(); - if (connection != null ) { - if (connection.getFeatures().csi()) { - connection.sendActive(); - } - if (broadcastLastActivity) { - sendPresence(account, false); //send new presence but don't include idle because we are not - } - } - } - } - Log.d(Config.LOGTAG, "app switched into foreground"); - } - - private void switchToBackground() { - final boolean broadcastLastActivity = broadcastLastActivity(); - for (Account account : getAccounts()) { - if (account.getStatus() == Account.State.ONLINE) { - XmppConnection connection = account.getXmppConnection(); - if (connection != null) { - if (broadcastLastActivity) { - sendPresence(account, broadcastLastActivity); - } - if (connection.getFeatures().csi()) { - connection.sendInactive(); - } - } - } - } - this.mNotificationService.setIsInForeground(false); - Log.d(Config.LOGTAG, "app switched into background"); - } - - private void connectMultiModeConversations(Account account) { - List conversations = getConversations(); - for (Conversation conversation : conversations) { - if (conversation.getMode() == Conversation.MODE_MULTI && conversation.getAccount() == account) { - joinMuc(conversation); - } - } - } - - public void joinMuc(Conversation conversation) { - joinMuc(conversation, null); - } - - private void joinMuc(Conversation conversation, final OnConferenceJoined onConferenceJoined) { - Account account = conversation.getAccount(); - account.pendingConferenceJoins.remove(conversation); - account.pendingConferenceLeaves.remove(conversation); - if (account.getStatus() == Account.State.ONLINE) { - conversation.resetMucOptions(); - if (onConferenceJoined != null) { - conversation.getMucOptions().flagNoAutoPushConfiguration(); - } - conversation.setHasMessagesLeftOnServer(false); - fetchConferenceConfiguration(conversation, new OnConferenceConfigurationFetched() { - - private void join(Conversation conversation) { - Account account = conversation.getAccount(); - final MucOptions mucOptions = conversation.getMucOptions(); - final Jid joinJid = mucOptions.getSelf().getFullJid(); - Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": joining conversation " + joinJid.toString()); - PresencePacket packet = mPresenceGenerator.selfPresence(account, Presence.Status.ONLINE, mucOptions.nonanonymous()); - packet.setTo(joinJid); - Element x = packet.addChild("x", "http://jabber.org/protocol/muc"); - if (conversation.getMucOptions().getPassword() != null) { - x.addChild("password").setContent(mucOptions.getPassword()); - } - - if (mucOptions.mamSupport()) { - // Use MAM instead of the limited muc history to get history - x.addChild("history").setAttribute("maxchars", "0"); - } else { - // Fallback to muc history - x.addChild("history").setAttribute("since", PresenceGenerator.getTimestamp(conversation.getLastMessageTransmitted())); - } - sendPresencePacket(account, packet); - if (onConferenceJoined != null) { - onConferenceJoined.onConferenceJoined(conversation); - } - if (!joinJid.equals(conversation.getJid())) { - conversation.setContactJid(joinJid); - databaseBackend.updateConversation(conversation); - } - - if (mucOptions.mamSupport()) { - getMessageArchiveService().catchupMUC(conversation); - } - if (mucOptions.membersOnly() && mucOptions.nonanonymous()) { - fetchConferenceMembers(conversation); - } - sendUnsentMessages(conversation); - } - - @Override - public void onConferenceConfigurationFetched(Conversation conversation) { - join(conversation); - } - - @Override - public void onFetchFailed(final Conversation conversation, Element error) { - if (error != null && "remote-server-not-found".equals(error.getName())) { - conversation.getMucOptions().setError(MucOptions.Error.SERVER_NOT_FOUND); - } else { - join(conversation); - fetchConferenceConfiguration(conversation); - } - } - }); - updateConversationUi(); - } else { - account.pendingConferenceJoins.add(conversation); - conversation.resetMucOptions(); - conversation.setHasMessagesLeftOnServer(false); - updateConversationUi(); - } - } - - private void fetchConferenceMembers(final Conversation conversation) { - final Account account = conversation.getAccount(); - final String[] affiliations = {"member","admin","owner"}; - OnIqPacketReceived callback = new OnIqPacketReceived() { - - private int i = 0; - - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - - Element query = packet.query("http://jabber.org/protocol/muc#admin"); - if (packet.getType() == IqPacket.TYPE.RESULT && query != null) { - for(Element child : query.getChildren()) { - if ("item".equals(child.getName())) { - MucOptions.User user = AbstractParser.parseItem(conversation,child); - if (!user.realJidMatchesAccount()) { - conversation.getMucOptions().updateUser(user); - } - } - } - } else { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not request affiliation "+affiliations[i]+" in "+conversation.getJid().toBareJid()); - } - ++i; - if (i >= affiliations.length) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": retrieved members for "+conversation.getJid().toBareJid()+": "+conversation.getMucOptions().getMembers()); - getAvatarService().clear(conversation); - updateMucRosterUi(); - updateConversationUi(); - } - } - }; - for(String affiliation : affiliations) { - sendIqPacket(account, mIqGenerator.queryAffiliation(conversation, affiliation), callback); - } - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": fetching members for "+conversation.getName()); - } - - public void providePasswordForMuc(Conversation conversation, String password) { - if (conversation.getMode() == Conversation.MODE_MULTI) { - conversation.getMucOptions().setPassword(password); - if (conversation.getBookmark() != null) { - if (respectAutojoin()) { - conversation.getBookmark().setAutojoin(true); - } - pushBookmarks(conversation.getAccount()); - } - updateConversation(conversation); - joinMuc(conversation); - } - } - - public void renameInMuc(final Conversation conversation, final String nick, final UiCallback callback) { - final MucOptions options = conversation.getMucOptions(); - final Jid joinJid = options.createJoinJid(nick); - if (options.online()) { - Account account = conversation.getAccount(); - options.setOnRenameListener(new OnRenameListener() { - - @Override - public void onSuccess() { - conversation.setContactJid(joinJid); - databaseBackend.updateConversation(conversation); - Bookmark bookmark = conversation.getBookmark(); - if (bookmark != null) { - bookmark.setNick(nick); - pushBookmarks(bookmark.getAccount()); - } - callback.success(conversation); - } - - @Override - public void onFailure() { - callback.error(R.string.nick_in_use, conversation); - } - }); - - PresencePacket packet = new PresencePacket(); - packet.setTo(joinJid); - packet.setFrom(conversation.getAccount().getJid()); - - String sig = account.getPgpSignature(); - if (sig != null) { - packet.addChild("status").setContent("online"); - packet.addChild("x", "jabber:x:signed").setContent(sig); - } - sendPresencePacket(account, packet); - } else { - conversation.setContactJid(joinJid); - databaseBackend.updateConversation(conversation); - if (conversation.getAccount().getStatus() == Account.State.ONLINE) { - Bookmark bookmark = conversation.getBookmark(); - if (bookmark != null) { - bookmark.setNick(nick); - pushBookmarks(bookmark.getAccount()); - } - joinMuc(conversation); - } - } - } - - public void leaveMuc(Conversation conversation) { - leaveMuc(conversation, false); - } - - private void leaveMuc(Conversation conversation, boolean now) { - Account account = conversation.getAccount(); - account.pendingConferenceJoins.remove(conversation); - account.pendingConferenceLeaves.remove(conversation); - if (account.getStatus() == Account.State.ONLINE || now) { - PresencePacket packet = new PresencePacket(); - packet.setTo(conversation.getMucOptions().getSelf().getFullJid()); - packet.setFrom(conversation.getAccount().getJid()); - packet.setAttribute("type", "unavailable"); - sendPresencePacket(conversation.getAccount(), packet); - conversation.getMucOptions().setOffline(); - conversation.deregisterWithBookmark(); - Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() - + ": leaving muc " + conversation.getJid()); - } else { - account.pendingConferenceLeaves.add(conversation); - } - } - - private String findConferenceServer(final Account account) { - String server; - if (account.getXmppConnection() != null) { - server = account.getXmppConnection().getMucServer(); - if (server != null) { - return server; - } - } - for (Account other : getAccounts()) { - if (other != account && other.getXmppConnection() != null) { - server = other.getXmppConnection().getMucServer(); - if (server != null) { - return server; - } - } - } - return null; - } - - public void createAdhocConference(final Account account, - final String subject, - final Iterable jids, - final UiCallback callback) { - Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": creating adhoc conference with " + jids.toString()); - if (account.getStatus() == Account.State.ONLINE) { - try { - String server = findConferenceServer(account); - if (server == null) { - if (callback != null) { - callback.error(R.string.no_conference_server_found, null); - } - return; - } - final Jid jid = Jid.fromParts(new BigInteger(64, getRNG()).toString(Character.MAX_RADIX), server, null); - final Conversation conversation = findOrCreateConversation(account, jid, true); - joinMuc(conversation, new OnConferenceJoined() { - @Override - public void onConferenceJoined(final Conversation conversation) { - pushConferenceConfiguration(conversation, IqGenerator.defaultRoomConfiguration(), new OnConferenceOptionsPushed() { - @Override - public void onPushSucceeded() { - if (subject != null && !subject.trim().isEmpty()) { - pushSubjectToConference(conversation, subject.trim()); - } - for (Jid invite : jids) { - invite(conversation, invite); - } - if (account.countPresences() > 1) { - directInvite(conversation, account.getJid().toBareJid()); - } - saveConversationAsBookmark(conversation, subject); - if (callback != null) { - callback.success(conversation); - } - } - - @Override - public void onPushFailed() { - archiveConversation(conversation); - if (callback != null) { - callback.error(R.string.conference_creation_failed, conversation); - } - } - }); - } - }); - } catch (InvalidJidException e) { - if (callback != null) { - callback.error(R.string.conference_creation_failed, null); - } - } - } else { - if (callback != null) { - callback.error(R.string.not_connected_try_again, null); - } - } - } - - public void fetchConferenceConfiguration(final Conversation conversation) { - fetchConferenceConfiguration(conversation, null); - } - - public void fetchConferenceConfiguration(final Conversation conversation, final OnConferenceConfigurationFetched callback) { - IqPacket request = new IqPacket(IqPacket.TYPE.GET); - request.setTo(conversation.getJid().toBareJid()); - request.query("http://jabber.org/protocol/disco#info"); - sendIqPacket(conversation.getAccount(), request, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - Element query = packet.findChild("query","http://jabber.org/protocol/disco#info"); - if (packet.getType() == IqPacket.TYPE.RESULT && query != null) { - ArrayList features = new ArrayList<>(); - for (Element child : query.getChildren()) { - if (child != null && child.getName().equals("feature")) { - String var = child.getAttribute("var"); - if (var != null) { - features.add(var); - } - } - } - Element form = query.findChild("x", "jabber:x:data"); - if (form != null) { - conversation.getMucOptions().updateFormData(Data.parse(form)); - } - conversation.getMucOptions().updateFeatures(features); - if (callback != null) { - callback.onConferenceConfigurationFetched(conversation); - } - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": fetched muc configuration for "+conversation.getJid().toBareJid()+" - "+features.toString()); - updateConversationUi(); - } else if (packet.getType() == IqPacket.TYPE.ERROR) { - if (callback != null) { - callback.onFetchFailed(conversation, packet.getError()); - } - } - } - }); - } - - public void pushConferenceConfiguration(final Conversation conversation, final Bundle options, final OnConferenceOptionsPushed callback) { - IqPacket request = new IqPacket(IqPacket.TYPE.GET); - request.setTo(conversation.getJid().toBareJid()); - request.query("http://jabber.org/protocol/muc#owner"); - sendIqPacket(conversation.getAccount(), request, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - Data data = Data.parse(packet.query().findChild("x", "jabber:x:data")); - for (Field field : data.getFields()) { - if (options.containsKey(field.getFieldName())) { - field.setValue(options.getString(field.getFieldName())); - } - } - data.submit(); - IqPacket set = new IqPacket(IqPacket.TYPE.SET); - set.setTo(conversation.getJid().toBareJid()); - set.query("http://jabber.org/protocol/muc#owner").addChild(data); - sendIqPacket(account, set, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (callback != null) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - callback.onPushSucceeded(); - } else { - callback.onPushFailed(); - } - } - } - }); - } else { - if (callback != null) { - callback.onPushFailed(); - } - } - } - }); - } - - public void pushSubjectToConference(final Conversation conference, final String subject) { - MessagePacket packet = this.getMessageGenerator().conferenceSubject(conference, subject); - this.sendMessagePacket(conference.getAccount(), packet); - final MucOptions mucOptions = conference.getMucOptions(); - final MucOptions.User self = mucOptions.getSelf(); - if (!mucOptions.persistent() && self.getAffiliation().ranks(MucOptions.Affiliation.OWNER)) { - Bundle options = new Bundle(); - options.putString("muc#roomconfig_persistentroom", "1"); - this.pushConferenceConfiguration(conference, options, null); - } - } - - public void changeAffiliationInConference(final Conversation conference, Jid user, final MucOptions.Affiliation affiliation, final OnAffiliationChanged callback) { - final Jid jid = user.toBareJid(); - IqPacket request = this.mIqGenerator.changeAffiliation(conference, jid, affiliation.toString()); - sendIqPacket(conference.getAccount(), request, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - conference.getMucOptions().changeAffiliation(jid, affiliation); - getAvatarService().clear(conference); - callback.onAffiliationChangedSuccessful(jid); - } else { - callback.onAffiliationChangeFailed(jid, R.string.could_not_change_affiliation); - } - } - }); - } - - public void changeAffiliationsInConference(final Conversation conference, MucOptions.Affiliation before, MucOptions.Affiliation after) { - List jids = new ArrayList<>(); - for (MucOptions.User user : conference.getMucOptions().getUsers()) { - if (user.getAffiliation() == before && user.getRealJid() != null) { - jids.add(user.getRealJid()); - } - } - IqPacket request = this.mIqGenerator.changeAffiliation(conference, jids, after.toString()); - sendIqPacket(conference.getAccount(), request, mDefaultIqHandler); - } - - public void changeRoleInConference(final Conversation conference, final String nick, MucOptions.Role role, final OnRoleChanged callback) { - IqPacket request = this.mIqGenerator.changeRole(conference, nick, role.toString()); - Log.d(Config.LOGTAG, request.toString()); - sendIqPacket(conference.getAccount(), request, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - Log.d(Config.LOGTAG, packet.toString()); - if (packet.getType() == IqPacket.TYPE.RESULT) { - callback.onRoleChangedSuccessful(nick); - } else { - callback.onRoleChangeFailed(nick, R.string.could_not_change_role); - } - } - }); - } - - private void disconnect(Account account, boolean force) { - if ((account.getStatus() == Account.State.ONLINE) - || (account.getStatus() == Account.State.DISABLED)) { - final XmppConnection connection = account.getXmppConnection(); - if (!force) { - List conversations = getConversations(); - for (Conversation conversation : conversations) { - if (conversation.getAccount() == account) { - if (conversation.getMode() == Conversation.MODE_MULTI) { - leaveMuc(conversation, true); - } else { - if (conversation.endOtrIfNeeded()) { - Log.d(Config.LOGTAG, account.getJid().toBareJid() - + ": ended otr session with " - + conversation.getJid()); - } - } - } - } - sendOfflinePresence(account); - } - connection.disconnect(force); - } - } - - @Override - public IBinder onBind(Intent intent) { - return mBinder; - } - - public void updateMessage(Message message) { - databaseBackend.updateMessage(message); - updateConversationUi(); - } - - public void updateMessage(Message message, String uuid) { - databaseBackend.updateMessage(message, uuid); - updateConversationUi(); - } - - protected void syncDirtyContacts(Account account) { - for (Contact contact : account.getRoster().getContacts()) { - if (contact.getOption(Contact.Options.DIRTY_PUSH)) { - pushContactToServer(contact); - } - if (contact.getOption(Contact.Options.DIRTY_DELETE)) { - deleteContactOnServer(contact); - } - } - } - - public void createContact(Contact contact) { - boolean autoGrant = getPreferences().getBoolean("grant_new_contacts", true); - if (autoGrant) { - contact.setOption(Contact.Options.PREEMPTIVE_GRANT); - contact.setOption(Contact.Options.ASKING); - } - pushContactToServer(contact); - } - - public void onOtrSessionEstablished(Conversation conversation) { - final Account account = conversation.getAccount(); - final Session otrSession = conversation.getOtrSession(); - Log.d(Config.LOGTAG, - account.getJid().toBareJid() + " otr session established with " - + conversation.getJid() + "/" - + otrSession.getSessionID().getUserID()); - conversation.findUnsentMessagesWithEncryption(Message.ENCRYPTION_OTR, new Conversation.OnMessageFound() { - - @Override - public void onMessageFound(Message message) { - SessionID id = otrSession.getSessionID(); - try { - message.setCounterpart(Jid.fromString(id.getAccountID() + "/" + id.getUserID())); - } catch (InvalidJidException e) { - return; - } - if (message.needsUploading()) { - mJingleConnectionManager.createNewConnection(message); - } else { - MessagePacket outPacket = mMessageGenerator.generateOtrChat(message); - if (outPacket != null) { - mMessageGenerator.addDelay(outPacket, message.getTimeSent()); - message.setStatus(Message.STATUS_SEND); - databaseBackend.updateMessage(message); - sendMessagePacket(account, outPacket); - } - } - updateConversationUi(); - } - }); - } - - public boolean renewSymmetricKey(Conversation conversation) { - Account account = conversation.getAccount(); - byte[] symmetricKey = new byte[32]; - this.mRandom.nextBytes(symmetricKey); - Session otrSession = conversation.getOtrSession(); - if (otrSession != null) { - MessagePacket packet = new MessagePacket(); - packet.setType(MessagePacket.TYPE_CHAT); - packet.setFrom(account.getJid()); - MessageGenerator.addMessageHints(packet); - packet.setAttribute("to", otrSession.getSessionID().getAccountID() + "/" - + otrSession.getSessionID().getUserID()); - try { - packet.setBody(otrSession - .transformSending(CryptoHelper.FILETRANSFER - + CryptoHelper.bytesToHex(symmetricKey))[0]); - sendMessagePacket(account, packet); - conversation.setSymmetricKey(symmetricKey); - return true; - } catch (OtrException e) { - return false; - } - } - return false; - } - - public void pushContactToServer(final Contact contact) { - contact.resetOption(Contact.Options.DIRTY_DELETE); - contact.setOption(Contact.Options.DIRTY_PUSH); - final Account account = contact.getAccount(); - if (account.getStatus() == Account.State.ONLINE) { - final boolean ask = contact.getOption(Contact.Options.ASKING); - final boolean sendUpdates = contact - .getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST) - && contact.getOption(Contact.Options.PREEMPTIVE_GRANT); - final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); - iq.query(Xmlns.ROSTER).addChild(contact.asElement()); - account.getXmppConnection().sendIqPacket(iq, mDefaultIqHandler); - if (sendUpdates) { - sendPresencePacket(account, - mPresenceGenerator.sendPresenceUpdatesTo(contact)); - } - if (ask) { - sendPresencePacket(account, - mPresenceGenerator.requestPresenceUpdatesFrom(contact)); - } - } - } - - public void publishAvatar(Account account, Uri image, UiCallback callback) { - final Bitmap.CompressFormat format = Config.AVATAR_FORMAT; - final int size = Config.AVATAR_SIZE; - final Avatar avatar = getFileBackend().getPepAvatar(image, size, format); - if (avatar != null) { - avatar.height = size; - avatar.width = size; - if (format.equals(Bitmap.CompressFormat.WEBP)) { - avatar.type = "image/webp"; - } else if (format.equals(Bitmap.CompressFormat.JPEG)) { - avatar.type = "image/jpeg"; - } else if (format.equals(Bitmap.CompressFormat.PNG)) { - avatar.type = "image/png"; - } - if (!getFileBackend().save(avatar)) { - callback.error(R.string.error_saving_avatar, avatar); - return; - } - publishAvatar(account, avatar, callback); - } else { - callback.error(R.string.error_publish_avatar_converting, null); - } - } - - public void publishAvatar(Account account, final Avatar avatar, final UiCallback callback) { - final IqPacket packet = this.mIqGenerator.publishAvatar(avatar); - this.sendIqPacket(account, packet, new OnIqPacketReceived() { - - @Override - public void onIqPacketReceived(Account account, IqPacket result) { - if (result.getType() == IqPacket.TYPE.RESULT) { - final IqPacket packet = XmppConnectionService.this.mIqGenerator - .publishAvatarMetadata(avatar); - sendIqPacket(account, packet, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket result) { - if (result.getType() == IqPacket.TYPE.RESULT) { - if (account.setAvatar(avatar.getFilename())) { - getAvatarService().clear(account); - databaseBackend.updateAccount(account); - } - if (callback != null) { - callback.success(avatar); - } else { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": published avatar"); - } - } else { - if (callback != null) { - callback.error( - R.string.error_publish_avatar_server_reject, - avatar); - } - } - } - }); - } else { - if (callback != null) { - callback.error( - R.string.error_publish_avatar_server_reject, - avatar); - } - } - } - }); - } - - public void republishAvatarIfNeeded(Account account) { - if (account.getAxolotlService().isPepBroken()) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": skipping republication of avatar because pep is broken"); - return; - } - IqPacket packet = this.mIqGenerator.retrieveAvatarMetaData(null); - this.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 = fileBackend.getStoredPepAvatar(account.getAvatar()); - if (avatar != null) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": avatar on server was null. republishing"); - publishAvatar(account, fileBackend.getStoredPepAvatar(account.getAvatar()), null); - } else { - Log.e(Config.LOGTAG, account.getJid().toBareJid()+": error rereading avatar"); - } - } - } - } - }); - } - - public void fetchAvatar(Account account, Avatar avatar) { - fetchAvatar(account, avatar, null); - } - - public void fetchAvatar(Account account, final Avatar avatar, final UiCallback callback) { - final String KEY = generateFetchKey(account, avatar); - synchronized (this.mInProgressAvatarFetches) { - if (!this.mInProgressAvatarFetches.contains(KEY)) { - switch (avatar.origin) { - case PEP: - this.mInProgressAvatarFetches.add(KEY); - fetchAvatarPep(account, avatar, callback); - break; - case VCARD: - this.mInProgressAvatarFetches.add(KEY); - fetchAvatarVcard(account, avatar, callback); - break; - } - } - } - } - - private void fetchAvatarPep(Account account, final Avatar avatar, final UiCallback callback) { - IqPacket packet = this.mIqGenerator.retrievePepAvatar(avatar); - sendIqPacket(account, packet, new OnIqPacketReceived() { - - @Override - public void onIqPacketReceived(Account account, IqPacket result) { - synchronized (mInProgressAvatarFetches) { - mInProgressAvatarFetches.remove(generateFetchKey(account, avatar)); - } - final String ERROR = account.getJid().toBareJid() - + ": fetching avatar for " + avatar.owner + " failed "; - if (result.getType() == IqPacket.TYPE.RESULT) { - avatar.image = mIqParser.avatarData(result); - if (avatar.image != null) { - if (getFileBackend().save(avatar)) { - if (account.getJid().toBareJid().equals(avatar.owner)) { - if (account.setAvatar(avatar.getFilename())) { - databaseBackend.updateAccount(account); - } - getAvatarService().clear(account); - updateConversationUi(); - updateAccountUi(); - } else { - Contact contact = account.getRoster() - .getContact(avatar.owner); - contact.setAvatar(avatar); - getAvatarService().clear(contact); - updateConversationUi(); - updateRosterUi(); - } - if (callback != null) { - callback.success(avatar); - } - Log.d(Config.LOGTAG, account.getJid().toBareJid() - + ": successfully fetched pep avatar for " + avatar.owner); - return; - } - } else { - - Log.d(Config.LOGTAG, ERROR + "(parsing error)"); - } - } else { - Element error = result.findChild("error"); - if (error == null) { - Log.d(Config.LOGTAG, ERROR + "(server error)"); - } else { - Log.d(Config.LOGTAG, ERROR + error.toString()); - } - } - if (callback != null) { - callback.error(0, null); - } - - } - }); - } - - private void fetchAvatarVcard(final Account account, final Avatar avatar, final UiCallback callback) { - IqPacket packet = this.mIqGenerator.retrieveVcardAvatar(avatar); - this.sendIqPacket(account, packet, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - synchronized (mInProgressAvatarFetches) { - mInProgressAvatarFetches.remove(generateFetchKey(account, avatar)); - } - if (packet.getType() == IqPacket.TYPE.RESULT) { - Element vCard = packet.findChild("vCard", "vcard-temp"); - Element photo = vCard != null ? vCard.findChild("PHOTO") : null; - String image = photo != null ? photo.findChildContent("BINVAL") : null; - if (image != null) { - avatar.image = image; - if (getFileBackend().save(avatar)) { - Log.d(Config.LOGTAG, account.getJid().toBareJid() - + ": successfully fetched vCard avatar for " + avatar.owner); - if (avatar.owner.isBareJid()) { - if (account.getJid().toBareJid().equals(avatar.owner) && account.getAvatar() == null) { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": had no avatar. replacing with vcard"); - account.setAvatar(avatar.getFilename()); - databaseBackend.updateAccount(account); - getAvatarService().clear(account); - updateAccountUi(); - } else { - Contact contact = account.getRoster().getContact(avatar.owner); - contact.setAvatar(avatar); - getAvatarService().clear(contact); - updateRosterUi(); - } - updateConversationUi(); - } else { - Conversation conversation = find(account, avatar.owner.toBareJid()); - if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) { - MucOptions.User user = conversation.getMucOptions().findUserByFullJid(avatar.owner); - if (user != null) { - if (user.setAvatar(avatar)) { - getAvatarService().clear(user); - updateConversationUi(); - updateMucRosterUi(); - } - } - } - } - } - } - } - } - }); - } - - public void checkForAvatar(Account account, final UiCallback callback) { - IqPacket packet = this.mIqGenerator.retrieveAvatarMetaData(null); - this.sendIqPacket(account, packet, new OnIqPacketReceived() { - - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - Element pubsub = packet.findChild("pubsub","http://jabber.org/protocol/pubsub"); - if (pubsub != null) { - Element items = pubsub.findChild("items"); - if (items != null) { - Avatar avatar = Avatar.parseMetadata(items); - if (avatar != null) { - avatar.owner = account.getJid().toBareJid(); - if (fileBackend.isAvatarCached(avatar)) { - if (account.setAvatar(avatar.getFilename())) { - databaseBackend.updateAccount(account); - } - getAvatarService().clear(account); - callback.success(avatar); - } else { - fetchAvatarPep(account, avatar, callback); - } - return; - } - } - } - } - callback.error(0, null); - } - }); - } - - public void deleteContactOnServer(Contact contact) { - contact.resetOption(Contact.Options.PREEMPTIVE_GRANT); - contact.resetOption(Contact.Options.DIRTY_PUSH); - contact.setOption(Contact.Options.DIRTY_DELETE); - Account account = contact.getAccount(); - if (account.getStatus() == Account.State.ONLINE) { - IqPacket iq = new IqPacket(IqPacket.TYPE.SET); - Element item = iq.query(Xmlns.ROSTER).addChild("item"); - item.setAttribute("jid", contact.getJid().toString()); - item.setAttribute("subscription", "remove"); - account.getXmppConnection().sendIqPacket(iq, mDefaultIqHandler); - } - } - - public void updateConversation(final Conversation conversation) { - mDatabaseExecutor.execute(new Runnable() { - @Override - public void run() { - databaseBackend.updateConversation(conversation); - } - }); - } - - private void reconnectAccount(final Account account, final boolean force, final boolean interactive) { - synchronized (account) { - XmppConnection connection = account.getXmppConnection(); - if (connection == null) { - connection = createConnection(account); - account.setXmppConnection(connection); - } else { - connection.interrupt(); - } - if (!account.isOptionSet(Account.OPTION_DISABLED)) { - if (!force) { - disconnect(account, false); - } - Thread thread = new Thread(connection); - connection.setInteractive(interactive); - connection.prepareNewConnection(); - thread.start(); - scheduleWakeUpCall(Config.CONNECT_DISCO_TIMEOUT, account.getUuid().hashCode()); - } else { - disconnect(account, force); - account.getRoster().clearPresences(); - connection.resetEverything(); - account.getAxolotlService().resetBrokenness(); - } - } - } - - public void reconnectAccountInBackground(final Account account) { - new Thread(new Runnable() { - @Override - public void run() { - reconnectAccount(account, false, true); - } - }).start(); - } - - public void invite(Conversation conversation, Jid contact) { - Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + ": inviting " + contact + " to " + conversation.getJid().toBareJid()); - MessagePacket packet = mMessageGenerator.invite(conversation, contact); - sendMessagePacket(conversation.getAccount(), packet); - } - - public void directInvite(Conversation conversation, Jid jid) { - MessagePacket packet = mMessageGenerator.directInvite(conversation, jid); - sendMessagePacket(conversation.getAccount(), packet); - } - - public void resetSendingToWaiting(Account account) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": reset 'sending' messages to 'waiting'"); - for (Conversation conversation : getConversations()) { - if (conversation.getAccount() == account) { - conversation.findUnsentTextMessages(new Conversation.OnMessageFound() { - - @Override - public void onMessageFound(Message message) { - markMessage(message, Message.STATUS_WAITING); - } - }); - } - } - } - - public Message markMessage(final Account account, final Jid recipient, final String uuid, final int status) { - return markMessage(account, recipient, uuid, status, null); - } - - public Message markMessage(final Account account, final Jid recipient, final String uuid, final int status, String errorMessage) { - if (uuid == null) { - return null; - } - for (Conversation conversation : getConversations()) { - if (conversation.getJid().toBareJid().equals(recipient) && conversation.getAccount() == account) { - final Message message = conversation.findSentMessageWithUuidOrRemoteId(uuid); - if (message != null) { - markMessage(message, status, errorMessage); - } - return message; - } - } - return null; - } - - public boolean markMessage(Conversation conversation, String uuid, int status) { - if (uuid == null) { - return false; - } else { - Message message = conversation.findSentMessageWithUuid(uuid); - if (message != null) { - markMessage(message, status); - return true; - } else { - return false; - } - } - } - - public void markMessage(Message message, int status) { - markMessage(message, status, null); - } - - public void markMessage(Message message, int status, String errorMessage) { - if (status == Message.STATUS_SEND_FAILED - && (message.getStatus() == Message.STATUS_SEND_RECEIVED || message - .getStatus() == Message.STATUS_SEND_DISPLAYED)) { - return; - } - message.setErrorMessage(errorMessage); - message.setStatus(status); - databaseBackend.updateMessage(message); - updateConversationUi(); - } - - public SharedPreferences getPreferences() { - return PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()); - } - - public boolean confirmMessages() { - return getPreferences().getBoolean("confirm_messages", true); - } - - public boolean allowMessageCorrection() { - return getPreferences().getBoolean("allow_message_correction", true); - } - - public boolean sendChatStates() { - return getPreferences().getBoolean("chat_states", true); - } - - public boolean saveEncryptedMessages() { - return !getPreferences().getBoolean("dont_save_encrypted", false); - } - - private boolean respectAutojoin() { - return getPreferences().getBoolean("autojoin", true); - } - - public boolean indicateReceived() { - return getPreferences().getBoolean("indicate_received", true); - } - - public boolean useTorToConnect() { - return Config.FORCE_ORBOT || getPreferences().getBoolean("use_tor", false); - } - - public boolean showExtendedConnectionOptions() { - return getPreferences().getBoolean("show_connection_options", false); - } - - public boolean broadcastLastActivity() { - return getPreferences().getBoolean("last_activity", true); - } - - public int unreadCount() { - int count = 0; - for (Conversation conversation : getConversations()) { - count += conversation.unreadCount(); - } - return count; - } - - public void vibrate() { - Log.d(Config.LOGTAG,"Notification: short vibrate"); - Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE); - vibrator.vibrate(100); - } - - - public void showErrorToastInUi(int resId) { - if (mOnShowErrorToast != null) { - mOnShowErrorToast.onShowErrorToast(resId); - } - } - - public void updateConversationUi() { - if (mOnConversationUpdate != null) { - mOnConversationUpdate.onConversationUpdate(); - } - } - - public void updateAccountUi() { - if (mOnAccountUpdate != null) { - mOnAccountUpdate.onAccountUpdate(); - } - } - - public void updateRosterUi() { - if (mOnRosterUpdate != null) { - mOnRosterUpdate.onRosterUpdate(); - } - } - - public boolean displayCaptchaRequest(Account account, String id, Data data, Bitmap captcha) { - if (mOnCaptchaRequested != null) { - DisplayMetrics metrics = getApplicationContext().getResources().getDisplayMetrics(); - Bitmap scaled = Bitmap.createScaledBitmap(captcha, (int) (captcha.getWidth() * metrics.scaledDensity), - (int) (captcha.getHeight() * metrics.scaledDensity), false); - - mOnCaptchaRequested.onCaptchaRequested(account, id, data, scaled); - return true; - } - return false; - } - - public void updateBlocklistUi(final OnUpdateBlocklist.Status status) { - if (mOnUpdateBlocklist != null) { - mOnUpdateBlocklist.OnUpdateBlocklist(status); - } - } - - public void updateMucRosterUi() { - if (mOnMucRosterUpdate != null) { - mOnMucRosterUpdate.onMucRosterUpdate(); - } - } - - public void keyStatusUpdated(AxolotlService.FetchStatus report) { - if (mOnKeyStatusUpdated != null) { - mOnKeyStatusUpdated.onKeyStatusUpdated(report); - } - } - - public Account findAccountByJid(final Jid accountJid) { - for (Account account : this.accounts) { - if (account.getJid().toBareJid().equals(accountJid.toBareJid())) { - return account; - } - } - return null; - } - - public Conversation findConversationByUuid(String uuid) { - for (Conversation conversation : getConversations()) { - if (conversation.getUuid().equals(uuid)) { - return conversation; - } - } - return null; - } - - public boolean markRead(final Conversation conversation) { - return markRead(conversation,true); - } - - public boolean markRead(final Conversation conversation, boolean clear) { - if (clear) { - mNotificationService.clear(conversation); - } - final List readMessages = conversation.markRead(); - if (readMessages.size() > 0) { - Runnable runnable = new Runnable() { - @Override - public void run() { - for (Message message : readMessages) { - databaseBackend.updateMessage(message); - } - } - }; - mDatabaseExecutor.execute(runnable); - updateUnreadCountBadge(); - return true; - } else { - return false; - } - } - - public synchronized void updateUnreadCountBadge() { - int count = unreadCount(); - if (unreadCount != count) { - Log.d(Config.LOGTAG, "update unread count to " + count); - if (count > 0) { - ShortcutBadger.applyCount(getApplicationContext(), count); - } else { - ShortcutBadger.removeCount(getApplicationContext()); - } - unreadCount = count; - } - } - - public void sendReadMarker(final Conversation conversation) { - final Message markable = conversation.getLatestMarkableMessage(); - if (this.markRead(conversation)) { - updateConversationUi(); - } - if (confirmMessages() && markable != null && markable.trusted() && markable.getRemoteMsgId() != null) { - Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + ": sending read marker to " + markable.getCounterpart().toString()); - Account account = conversation.getAccount(); - final Jid to = markable.getCounterpart(); - MessagePacket packet = mMessageGenerator.confirm(account, to, markable.getRemoteMsgId()); - this.sendMessagePacket(conversation.getAccount(), packet); - } - } - - public SecureRandom getRNG() { - return this.mRandom; - } - - public MemorizingTrustManager getMemorizingTrustManager() { - return this.mMemorizingTrustManager; - } - - public void setMemorizingTrustManager(MemorizingTrustManager trustManager) { - this.mMemorizingTrustManager = trustManager; - } - - public void updateMemorizingTrustmanager() { - final MemorizingTrustManager tm; - final boolean dontTrustSystemCAs = getPreferences().getBoolean("dont_trust_system_cas", false); - if (dontTrustSystemCAs) { - tm = new MemorizingTrustManager(getApplicationContext(), null); - } else { - tm = new MemorizingTrustManager(getApplicationContext()); - } - setMemorizingTrustManager(tm); - } - - public PowerManager getPowerManager() { - return this.pm; - } - - public LruCache getBitmapCache() { - return this.mBitmapCache; - } - - public void syncRosterToDisk(final Account account) { - Runnable runnable = new Runnable() { - - @Override - public void run() { - databaseBackend.writeRoster(account.getRoster()); - } - }; - mDatabaseExecutor.execute(runnable); - - } - - public List getKnownHosts() { - final List hosts = new ArrayList<>(); - for (final Account account : getAccounts()) { - if (!hosts.contains(account.getServer().toString())) { - hosts.add(account.getServer().toString()); - } - for (final Contact contact : account.getRoster().getContacts()) { - if (contact.showInRoster()) { - final String server = contact.getServer().toString(); - if (server != null && !hosts.contains(server)) { - hosts.add(server); - } - } - } - } - if(Config.DOMAIN_LOCK != null && !hosts.contains(Config.DOMAIN_LOCK)) { - hosts.add(Config.DOMAIN_LOCK); - } - if(Config.MAGIC_CREATE_DOMAIN != null && !hosts.contains(Config.MAGIC_CREATE_DOMAIN)) { - hosts.add(Config.MAGIC_CREATE_DOMAIN); - } - return hosts; - } - - public List getKnownConferenceHosts() { - final ArrayList mucServers = new ArrayList<>(); - for (final Account account : accounts) { - if (account.getXmppConnection() != null) { - final String server = account.getXmppConnection().getMucServer(); - if (server != null && !mucServers.contains(server)) { - mucServers.add(server); - } - } - } - return mucServers; - } - - public void sendMessagePacket(Account account, MessagePacket packet) { - XmppConnection connection = account.getXmppConnection(); - if (connection != null) { - connection.sendMessagePacket(packet); - } - } - - public void sendPresencePacket(Account account, PresencePacket packet) { - XmppConnection connection = account.getXmppConnection(); - if (connection != null) { - connection.sendPresencePacket(packet); - } - } - - public void sendCreateAccountWithCaptchaPacket(Account account, String id, Data data) { - final XmppConnection connection = account.getXmppConnection(); - if (connection != null) { - IqPacket request = mIqGenerator.generateCreateAccountWithCaptcha(account, id, data); - connection.sendUnmodifiedIqPacket(request, connection.registrationResponseListener); - } - } - - public void sendIqPacket(final Account account, final IqPacket packet, final OnIqPacketReceived callback) { - final XmppConnection connection = account.getXmppConnection(); - if (connection != null) { - connection.sendIqPacket(packet, callback); - } - } - - public void sendPresence(final Account account) { - sendPresence(account, checkListeners() && broadcastLastActivity()); - } - - private void sendPresence(final Account account, final boolean includeIdleTimestamp) { - PresencePacket packet; - if (manuallyChangePresence()) { - packet = mPresenceGenerator.selfPresence(account, account.getPresenceStatus()); - String message = account.getPresenceStatusMessage(); - if (message != null && !message.isEmpty()) { - packet.addChild(new Element("status").setContent(message)); - } - } else { - packet = mPresenceGenerator.selfPresence(account, getTargetPresence()); - } - if (mLastActivity > 0 && includeIdleTimestamp) { - long since = Math.min(mLastActivity, System.currentTimeMillis()); //don't send future dates - packet.addChild("idle","urn:xmpp:idle:1").setAttribute("since", AbstractGenerator.getTimestamp(since)); - } - sendPresencePacket(account, packet); - } - - private void deactivateGracePeriod() { - for(Account account : getAccounts()) { - account.deactivateGracePeriod(); - } - } - - public void refreshAllPresences() { - boolean includeIdleTimestamp = checkListeners() && broadcastLastActivity(); - for (Account account : getAccounts()) { - if (!account.isOptionSet(Account.OPTION_DISABLED)) { - sendPresence(account, includeIdleTimestamp); - } - } - } - - private void refreshAllGcmTokens() { - for(Account account : getAccounts()) { - if (account.isOnlineAndConnected() && mPushManagementService.available(account)) { - mPushManagementService.registerPushTokenOnServer(account); - } - } - } - - private void sendOfflinePresence(final Account account) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": sending offline presence"); - sendPresencePacket(account, mPresenceGenerator.sendOfflinePresence(account)); - } - - public MessageGenerator getMessageGenerator() { - return this.mMessageGenerator; - } - - public PresenceGenerator getPresenceGenerator() { - return this.mPresenceGenerator; - } - - public IqGenerator getIqGenerator() { - return this.mIqGenerator; - } - - public IqParser getIqParser() { - return this.mIqParser; - } - - public JingleConnectionManager getJingleConnectionManager() { - return this.mJingleConnectionManager; - } - - public MessageArchiveService getMessageArchiveService() { - return this.mMessageArchiveService; - } - - public List findContacts(Jid jid) { - ArrayList contacts = new ArrayList<>(); - for (Account account : getAccounts()) { - if (!account.isOptionSet(Account.OPTION_DISABLED)) { - Contact contact = account.getRoster().getContactFromRoster(jid); - if (contact != null) { - contacts.add(contact); - } - } - } - return contacts; - } - - public Conversation findFirstMuc(Jid jid) { - for(Conversation conversation : getConversations()) { - if (conversation.getJid().toBareJid().equals(jid.toBareJid()) - && conversation.getMode() == Conversation.MODE_MULTI) { - return conversation; - } - } - return null; - } - - public NotificationService getNotificationService() { - return this.mNotificationService; - } - - public HttpConnectionManager getHttpConnectionManager() { - return this.mHttpConnectionManager; - } - - public void resendFailedMessages(final Message message) { - final Collection messages = new ArrayList<>(); - Message current = message; - while (current.getStatus() == Message.STATUS_SEND_FAILED) { - messages.add(current); - if (current.mergeable(current.next())) { - current = current.next(); - } else { - break; - } - } - for (final Message msg : messages) { - msg.setTime(System.currentTimeMillis()); - markMessage(msg, Message.STATUS_WAITING); - this.resendMessage(msg, false); - } - } - - public void clearConversationHistory(final Conversation conversation) { - conversation.clearMessages(); - conversation.setHasMessagesLeftOnServer(false); //avoid messages getting loaded through mam - conversation.setLastClearHistory(System.currentTimeMillis()); - Runnable runnable = new Runnable() { - @Override - public void run() { - databaseBackend.deleteMessagesInConversation(conversation); - databaseBackend.updateConversation(conversation); - - } - }; - mDatabaseExecutor.execute(runnable); - } - - public void sendBlockRequest(final Blockable blockable, boolean reportSpam) { - if (blockable != null && blockable.getBlockedJid() != null) { - final Jid jid = blockable.getBlockedJid(); - this.sendIqPacket(blockable.getAccount(), getIqGenerator().generateSetBlockRequest(jid, reportSpam), new OnIqPacketReceived() { - - @Override - public void onIqPacketReceived(final Account account, final IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - account.getBlocklist().add(jid); - updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED); - } - } - }); - } - } - - public void sendUnblockRequest(final Blockable blockable) { - if (blockable != null && blockable.getJid() != null) { - final Jid jid = blockable.getBlockedJid(); - this.sendIqPacket(blockable.getAccount(), getIqGenerator().generateSetUnblockRequest(jid), new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(final Account account, final IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - account.getBlocklist().remove(jid); - updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED); - } - } - }); - } - } - - public void publishDisplayName(Account account) { - String displayName = account.getDisplayName(); - if (displayName != null && !displayName.isEmpty()) { - IqPacket publish = mIqGenerator.publishNick(displayName); - sendIqPacket(account, publish, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.ERROR) { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not publish nick"); - } - } - }); - } - } - - public ServiceDiscoveryResult getCachedServiceDiscoveryResult(Pair key) { - ServiceDiscoveryResult result = discoCache.get(key); - if (result != null) { - return result; - } else { - result = databaseBackend.findDiscoveryResult(key.first, key.second); - if (result != null) { - discoCache.put(key, result); - } - return result; - } - } - - public void fetchCaps(Account account, final Jid jid, final Presence presence) { - final Pair key = new Pair<>(presence.getHash(), presence.getVer()); - ServiceDiscoveryResult disco = getCachedServiceDiscoveryResult(key); - if (disco != null) { - presence.setServiceDiscoveryResult(disco); - } else { - if (!account.inProgressDiscoFetches.contains(key)) { - account.inProgressDiscoFetches.add(key); - IqPacket request = new IqPacket(IqPacket.TYPE.GET); - request.setTo(jid); - request.query("http://jabber.org/protocol/disco#info"); - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": making disco request for "+key.second+" to "+jid); - sendIqPacket(account, request, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket discoPacket) { - if (discoPacket.getType() == IqPacket.TYPE.RESULT) { - ServiceDiscoveryResult disco = new ServiceDiscoveryResult(discoPacket); - if (presence.getVer().equals(disco.getVer())) { - databaseBackend.insertDiscoveryResult(disco); - injectServiceDiscorveryResult(account.getRoster(), presence.getHash(), presence.getVer(), disco); - } else { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": mismatch in caps for contact " + jid + " " + presence.getVer() + " vs " + disco.getVer()); - } - } - account.inProgressDiscoFetches.remove(key); - } - }); - } - } - } - - private void injectServiceDiscorveryResult(Roster roster, String hash, String ver, ServiceDiscoveryResult disco) { - for(Contact contact : roster.getContacts()) { - for(Presence presence : contact.getPresences().getPresences().values()) { - if (hash.equals(presence.getHash()) && ver.equals(presence.getVer())) { - presence.setServiceDiscoveryResult(disco); - } - } - } - } - - public void fetchMamPreferences(Account account, final OnMamPreferencesFetched callback) { - IqPacket request = new IqPacket(IqPacket.TYPE.GET); - request.addChild("prefs","urn:xmpp:mam:0"); - sendIqPacket(account, request, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - Element prefs = packet.findChild("prefs","urn:xmpp:mam:0"); - if (packet.getType() == IqPacket.TYPE.RESULT && prefs != null) { - callback.onPreferencesFetched(prefs); - } else { - callback.onPreferencesFetchFailed(); - } - } - }); - } - - public PushManagementService getPushManagementService() { - return mPushManagementService; - } - - public Account getPendingAccount() { - Account pending = null; - for(Account account : getAccounts()) { - if (account.isOptionSet(Account.OPTION_REGISTER)) { - pending = account; - } else { - return null; - } - } - return pending; - } - - public void changeStatus(Account account, Presence.Status status, String statusMessage, boolean send) { - if (!statusMessage.isEmpty()) { - databaseBackend.insertPresenceTemplate(new PresenceTemplate(status, statusMessage)); - } - changeStatusReal(account, status, statusMessage, send); - } - - private void changeStatusReal(Account account, Presence.Status status, String statusMessage, boolean send) { - account.setPresenceStatus(status); - account.setPresenceStatusMessage(statusMessage); - databaseBackend.updateAccount(account); - if (!account.isOptionSet(Account.OPTION_DISABLED) && send) { - sendPresence(account); - } - } - - public void changeStatus(Presence.Status status, String statusMessage) { - if (!statusMessage.isEmpty()) { - databaseBackend.insertPresenceTemplate(new PresenceTemplate(status, statusMessage)); - } - for(Account account : getAccounts()) { - changeStatusReal(account, status, statusMessage, true); - } - } - - public List getPresenceTemplates(Account account) { - List templates = databaseBackend.getPresenceTemplates(); - for(PresenceTemplate template : account.getSelfContact().getPresences().asTemplates()) { - if (!templates.contains(template)) { - templates.add(0, template); - } - } - return templates; - } - - public void saveConversationAsBookmark(Conversation conversation, String name) { - Account account = conversation.getAccount(); - Bookmark bookmark = new Bookmark(account, conversation.getJid().toBareJid()); - if (!conversation.getJid().isBareJid()) { - bookmark.setNick(conversation.getJid().getResourcepart()); - } - if (name != null && !name.trim().isEmpty()) { - bookmark.setBookmarkName(name.trim()); - } - bookmark.setAutojoin(getPreferences().getBoolean("autojoin",true)); - account.getBookmarks().add(bookmark); - pushBookmarks(account); - conversation.setBookmark(bookmark); - } - - public void clearStartTimeCounter() { - mDatabaseExecutor.execute(new Runnable() { - @Override - public void run() { - databaseBackend.clearStartTimeCounter(); - } - }); - } - - public void verifyFingerprints(Contact contact, List fingerprints) { - boolean needsRosterWrite = false; - final AxolotlService axolotlService = contact.getAccount().getAxolotlService(); - for(XmppUri.Fingerprint fp : fingerprints) { - if (fp.type == XmppUri.FingerprintType.OTR) { - needsRosterWrite |= contact.addOtrFingerprint(fp.fingerprint); - } else if (fp.type == XmppUri.FingerprintType.OMEMO) { - String fingerprint = "05"+fp.fingerprint.replaceAll("\\s",""); - FingerprintStatus fingerprintStatus = axolotlService.getFingerprintTrust(fingerprint); - if (fingerprintStatus != null) { - if (!fingerprintStatus.isVerified()) { - axolotlService.setFingerprintTrust(fingerprint,fingerprintStatus.toVerified()); - } - } else { - axolotlService.preVerifyFingerprint(contact,fingerprint); - } - } - } - if (needsRosterWrite) { - syncRosterToDisk(contact.getAccount()); - } - } - - public boolean verifyFingerprints(Account account, List fingerprints) { - final AxolotlService axolotlService = account.getAxolotlService(); - boolean verifiedSomething = false; - for(XmppUri.Fingerprint fp : fingerprints) { - if (fp.type == XmppUri.FingerprintType.OMEMO) { - String fingerprint = "05"+fp.fingerprint.replaceAll("\\s",""); - Log.d(Config.LOGTAG,"trying to verify own fp="+fingerprint); - FingerprintStatus fingerprintStatus = axolotlService.getFingerprintTrust(fingerprint); - if (fingerprintStatus != null) { - if (!fingerprintStatus.isVerified()) { - axolotlService.setFingerprintTrust(fingerprint,fingerprintStatus.toVerified()); - verifiedSomething = true; - } - } else { - axolotlService.preVerifyFingerprint(account,fingerprint); - verifiedSomething = true; - } - } - } - return verifiedSomething; - } - - public interface OnMamPreferencesFetched { - void onPreferencesFetched(Element prefs); - void onPreferencesFetchFailed(); - } - - public void pushMamPreferences(Account account, Element prefs) { - IqPacket set = new IqPacket(IqPacket.TYPE.SET); - set.addChild(prefs); - sendIqPacket(account, set, null); - } - - public interface OnAccountCreated { - void onAccountCreated(Account account); - - void informUser(int r); - } - - public interface OnMoreMessagesLoaded { - void onMoreMessagesLoaded(int count, Conversation conversation); - - void informUser(int r); - } - - public interface OnAccountPasswordChanged { - void onPasswordChangeSucceeded(); - - void onPasswordChangeFailed(); - } - - public interface OnAffiliationChanged { - void onAffiliationChangedSuccessful(Jid jid); - - void onAffiliationChangeFailed(Jid jid, int resId); - } - - public interface OnRoleChanged { - void onRoleChangedSuccessful(String nick); - - void onRoleChangeFailed(String nick, int resid); - } - - public interface OnConversationUpdate { - void onConversationUpdate(); - } - - public interface OnAccountUpdate { - void onAccountUpdate(); - } - - public interface OnCaptchaRequested { - void onCaptchaRequested(Account account, - String id, - Data data, - Bitmap captcha); - } - - public interface OnRosterUpdate { - void onRosterUpdate(); - } - - public interface OnMucRosterUpdate { - void onMucRosterUpdate(); - } - - public interface OnConferenceConfigurationFetched { - void onConferenceConfigurationFetched(Conversation conversation); - - void onFetchFailed(Conversation conversation, Element error); - } - - public interface OnConferenceJoined { - void onConferenceJoined(Conversation conversation); - } - - public interface OnConferenceOptionsPushed { - void onPushSucceeded(); - - void onPushFailed(); - } - - public interface OnShowErrorToast { - void onShowErrorToast(int resId); - } - - public class XmppConnectionBinder extends Binder { - public XmppConnectionService getService() { - return XmppConnectionService.this; - } - } - - public void ScheduleAutomaticExport() { - //start export log service every day at given time - if (Config.ExportLogs) { - if (Config.ExportLogs_Hour >= 0 && Config.ExportLogs_Hour <= 23 && Config.ExportLogs_Minute >= 0 && Config.ExportLogs_Minute <= 59) { - Log.d(Config.LOGTAG, "Schedule automatic export logs at " + Config.ExportLogs_Hour + ":" + Config.ExportLogs_Minute); - Calendar calendar = Calendar.getInstance(); - calendar.setTimeInMillis(System.currentTimeMillis()); - calendar.set(Calendar.HOUR_OF_DAY, Config.ExportLogs_Hour); - calendar.set(Calendar.MINUTE, Config.ExportLogs_Minute); - Intent intent = new Intent(this, AlarmReceiver.class); - intent.setAction("exportlogs"); - final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, AlarmReceiver.SCHEDULE_ALARM_REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT); - ((AlarmManager) getSystemService(ALARM_SERVICE)).setInexactRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), AlarmManager.INTERVAL_DAY, pendingIntent); - } - } - } - - public void CancelAutomaticExport() { - if (Config.ExportLogs) { - Log.d(Config.LOGTAG, "Cancel scheduled automatic export"); - Intent intent = new Intent(this, AlarmReceiver.class); - final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, AlarmReceiver.SCHEDULE_ALARM_REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT); - ((AlarmManager) this.getSystemService(ALARM_SERVICE)).cancel(pendingIntent); - } - } + public static final String ACTION_REPLY_TO_CONVERSATION = "reply_to_conversations"; + public static final String ACTION_CLEAR_NOTIFICATION = "clear_notification"; + public static final String ACTION_DISABLE_FOREGROUND = "disable_foreground"; + public static final String ACTION_TRY_AGAIN = "try_again"; + public static final String ACTION_DISMISS_ERROR_NOTIFICATIONS = "dismiss_error"; + public static final String ACTION_IDLE_PING = "idle_ping"; + private static final String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts"; + public static final String ACTION_GCM_TOKEN_REFRESH = "gcm_token_refresh"; + public static final String ACTION_GCM_MESSAGE_RECEIVED = "gcm_message_received"; + private final SerialSingleThreadExecutor mFileAddingExecutor = new SerialSingleThreadExecutor(); + private final SerialSingleThreadExecutor mDatabaseExecutor = new SerialSingleThreadExecutor(); + private ReplacingSerialSingleThreadExecutor mContactMergerExecutor = new ReplacingSerialSingleThreadExecutor(true); + private final IBinder mBinder = new XmppConnectionBinder(); + private final List conversations = new CopyOnWriteArrayList<>(); + private final IqGenerator mIqGenerator = new IqGenerator(this); + private final List mInProgressAvatarFetches = new ArrayList<>(); + private final HashSet mLowPingTimeoutMode = new HashSet<>(); + private WakeLock wakeLock; + private long mLastActivity = 0; + public static VideoCompressor CompressVideo; + private NotificationManager mNotifyManager; + + public DatabaseBackend databaseBackend; + private ContentObserver contactObserver = new ContentObserver(null) { + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + Intent intent = new Intent(getApplicationContext(), + XmppConnectionService.class); + intent.setAction(ACTION_MERGE_PHONE_CONTACTS); + startService(intent); + } + }; + private FileBackend fileBackend = new FileBackend(this); + private MemorizingTrustManager mMemorizingTrustManager; + private NotificationService mNotificationService = new NotificationService( + this); + private OnMessagePacketReceived mMessageParser = new MessageParser(this); + private OnPresencePacketReceived mPresenceParser = new PresenceParser(this); + private IqParser mIqParser = new IqParser(this); + private OnIqPacketReceived mDefaultIqHandler = new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() != IqPacket.TYPE.RESULT) { + Element error = packet.findChild("error"); + String text = error != null ? error.findChildContent("text") : null; + if (text != null) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": received iq error - " + text); + } + } + } + }; + private MessageGenerator mMessageGenerator = new MessageGenerator(this); + private PresenceGenerator mPresenceGenerator = new PresenceGenerator(this); + private List accounts; + private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager( + this); + public OnContactStatusChanged onContactStatusChanged = new OnContactStatusChanged() { + + @Override + public void onContactStatusChanged(Contact contact, boolean online) { + Conversation conversation = find(getConversations(), contact); + if (conversation != null) { + if (online) { + conversation.endOtrIfNeeded(); + if (contact.getPresences().size() == 1) { + sendUnsentMessages(conversation); + } + } else { + //check if the resource we are haveing a conversation with is still online + if (conversation.hasValidOtrSession()) { + String otrResource = conversation.getOtrSession().getSessionID().getUserID(); + if (!(Arrays.asList(contact.getPresences().toResourceArray()).contains(otrResource))) { + conversation.endOtrIfNeeded(); + } + } + } + } + } + }; + private HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager( + this); + private AvatarService mAvatarService = new AvatarService(this); + private MessageArchiveService mMessageArchiveService = new MessageArchiveService(this); + private PushManagementService mPushManagementService = new PushManagementService(this); + private OnConversationUpdate mOnConversationUpdate = null; + + + private final ConversationsFileObserver fileObserver = new ConversationsFileObserver( + Environment.getExternalStorageDirectory().getAbsolutePath() + ) { + @Override + public void onEvent(int event, String path) { + markFileDeleted(path); + } + }; + private final OnJinglePacketReceived jingleListener = new OnJinglePacketReceived() { + + @Override + public void onJinglePacketReceived(Account account, JinglePacket packet) { + mJingleConnectionManager.deliverPacket(account, packet); + } + }; + private final OnMessageAcknowledged mOnMessageAcknowledgedListener = new OnMessageAcknowledged() { + + @Override + public void onMessageAcknowledged(Account account, String uuid) { + for (final Conversation conversation : getConversations()) { + if (conversation.getAccount() == account) { + Message message = conversation.findUnsentMessageWithUuid(uuid); + if (message != null) { + markMessage(message, Message.STATUS_SEND); + } + } + } + } + }; + private int convChangedListenerCount = 0; + private OnShowErrorToast mOnShowErrorToast = null; + private int showErrorToastListenerCount = 0; + private int unreadCount = -1; + private OnAccountUpdate mOnAccountUpdate = null; + private OnCaptchaRequested mOnCaptchaRequested = null; + private int accountChangedListenerCount = 0; + private int captchaRequestedListenerCount = 0; + private OnRosterUpdate mOnRosterUpdate = null; + private OnUpdateBlocklist mOnUpdateBlocklist = null; + private int updateBlocklistListenerCount = 0; + private int rosterChangedListenerCount = 0; + private OnMucRosterUpdate mOnMucRosterUpdate = null; + private int mucRosterChangedListenerCount = 0; + private OnKeyStatusUpdated mOnKeyStatusUpdated = null; + private int keyStatusUpdatedListenerCount = 0; + private SecureRandom mRandom; + private LruCache, ServiceDiscoveryResult> discoCache = new LruCache<>(20); + private final OnBindListener mOnBindListener = new OnBindListener() { + + @Override + public void onBind(final Account account) { + synchronized (mInProgressAvatarFetches) { + for (Iterator iterator = mInProgressAvatarFetches.iterator(); iterator.hasNext(); ) { + final String KEY = iterator.next(); + if (KEY.startsWith(account.getJid().toBareJid() + "_")) { + iterator.remove(); + } + } + } + account.getRoster().clearPresences(); + mJingleConnectionManager.cancelInTransmission(); + fetchRosterFromServer(account); + fetchBookmarks(account); + sendPresence(account); + if (mPushManagementService.available(account)) { + mPushManagementService.registerPushTokenOnServer(account); + } + connectMultiModeConversations(account); + syncDirtyContacts(account); + } + }; + private OnStatusChanged statusListener = new OnStatusChanged() { + + @Override + public void onStatusChanged(final Account account) { + XmppConnection connection = account.getXmppConnection(); + if (mOnAccountUpdate != null) { + mOnAccountUpdate.onAccountUpdate(); + } + if (account.getStatus() == Account.State.ONLINE) { + synchronized (mLowPingTimeoutMode) { + if (mLowPingTimeoutMode.remove(account.getJid().toBareJid())) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": leaving low ping timeout mode"); + } + } + if (account.setShowErrorNotification(true)) { + databaseBackend.updateAccount(account); + } + mMessageArchiveService.executePendingQueries(account); + if (connection != null && connection.getFeatures().csi()) { + if (checkListeners()) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + " sending csi//inactive"); + connection.sendInactive(); + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + " sending csi//active"); + connection.sendActive(); + } + } + List conversations = getConversations(); + for (Conversation conversation : conversations) { + if (conversation.getAccount() == account + && !account.pendingConferenceJoins.contains(conversation)) { + if (!conversation.startOtrIfNeeded()) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": couldn't start OTR with " + conversation.getContact().getJid() + " when needed"); + } + sendUnsentMessages(conversation); + } + } + for (Conversation conversation : account.pendingConferenceLeaves) { + leaveMuc(conversation); + } + account.pendingConferenceLeaves.clear(); + for (Conversation conversation : account.pendingConferenceJoins) { + joinMuc(conversation); + } + account.pendingConferenceJoins.clear(); + scheduleWakeUpCall(Config.PING_MAX_INTERVAL, account.getUuid().hashCode()); + } else if (account.getStatus() == Account.State.OFFLINE || account.getStatus() == Account.State.DISABLED) { + resetSendingToWaiting(account); + if (!account.isOptionSet(Account.OPTION_DISABLED)) { + synchronized (mLowPingTimeoutMode) { + if (mLowPingTimeoutMode.contains(account.getJid().toBareJid())) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": went into offline state during low ping mode. reconnecting now"); + reconnectAccount(account, true, false); + } else { + int timeToReconnect = mRandom.nextInt(20) + 10; + scheduleWakeUpCall(timeToReconnect, account.getUuid().hashCode()); + } + } + } + } else if (account.getStatus() == Account.State.REGISTRATION_SUCCESSFUL) { + databaseBackend.updateAccount(account); + reconnectAccount(account, true, false); + } else if ((account.getStatus() != Account.State.CONNECTING) + && (account.getStatus() != Account.State.NO_INTERNET)) { + resetSendingToWaiting(account); + if (connection != null) { + int next = connection.getTimeToNextAttempt(); + Log.d(Config.LOGTAG, account.getJid().toBareJid() + + ": error connecting account. try again in " + + next + "s for the " + + (connection.getAttempt() + 1) + " time"); + scheduleWakeUpCall(next, account.getUuid().hashCode()); + } + } + getNotificationService().updateErrorNotification(); + } + }; + private OpenPgpServiceConnection pgpServiceConnection; + private PgpEngine mPgpEngine = null; + private PowerManager pm; + private LruCache mBitmapCache; + private EventReceiver mEventReceiver = new EventReceiver(); + + private boolean mRestoredFromDatabase = false; + + private static String generateFetchKey(Account account, final Avatar avatar) { + return account.getJid().toBareJid() + "_" + avatar.owner + "_" + avatar.sha1sum; + } + + public boolean areMessagesInitialized() { + return this.mRestoredFromDatabase; + } + + public PgpEngine getPgpEngine() { + if (!Config.supportOpenPgp()) { + return null; + } else if (pgpServiceConnection != null && pgpServiceConnection.isBound()) { + if (this.mPgpEngine == null) { + this.mPgpEngine = new PgpEngine(new OpenPgpApi( + getApplicationContext(), + pgpServiceConnection.getService()), this); + } + return mPgpEngine; + } else { + return null; + } + + } + + public OpenPgpApi getOpenPgpApi() { + if (!Config.supportOpenPgp()) { + return null; + } else if (pgpServiceConnection != null && pgpServiceConnection.isBound()) { + return new OpenPgpApi(this, pgpServiceConnection.getService()); + } else { + return null; + } + } + + public FileBackend getFileBackend() { + return this.fileBackend; + } + + public AvatarService getAvatarService() { + return this.mAvatarService; + } + + public void attachLocationToConversation(final Conversation conversation, + final Uri uri, + final UiCallback callback) { + int encryption = conversation.getNextEncryption(); + if (encryption == Message.ENCRYPTION_PGP) { + encryption = Message.ENCRYPTION_DECRYPTED; + } + Message message = new Message(conversation, uri.toString(), encryption); + if (conversation.getNextCounterpart() != null) { + message.setCounterpart(conversation.getNextCounterpart()); + } + if (encryption == Message.ENCRYPTION_DECRYPTED) { + getPgpEngine().encrypt(message, callback); + } else { + callback.success(message); + } + } + + public void attachFileToConversation(final Conversation conversation, + final Uri uri, + final UiCallback callback) { + if (FileBackend.weOwnFile(this, uri)) { + Log.d(Config.LOGTAG, "trying to attach file that belonged to us"); + callback.error(R.string.security_error_invalid_file_access, null); + return; + } + final Message message; + if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { + message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED); + } else { + message = new Message(conversation, "", conversation.getNextEncryption()); + } + message.setCounterpart(conversation.getNextCounterpart()); + message.setType(Message.TYPE_FILE); + final String path = getFileBackend().getOriginalPath(uri); + Log.d(Config.LOGTAG, "File path = " + path); + mFileAddingExecutor.execute(new Runnable() { + @Override + public void run() { + if (path != null) { + message.setRelativeFilePath(path); + getFileBackend().updateFileParams(message); + if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { + getPgpEngine().encrypt(message, callback); + } else { + callback.success(message); + } + } else { + try { + getFileBackend().copyFileToPrivateStorage(message, uri); + getFileBackend().updateFileParams(message); + if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { + final PgpEngine pgpEngine = getPgpEngine(); + if (pgpEngine != null) { + pgpEngine.encrypt(message, callback); + } else if (callback != null) { + callback.error(R.string.unable_to_connect_to_keychain, null); + } + } else { + callback.success(message); + } + } catch (FileBackend.FileCopyException e) { + callback.error(e.getResId(), message); + } + } + } + }); + } + + public void attachImageToConversation(final Conversation conversation, final Uri uri, final UiCallback callback) { + if (FileBackend.weOwnFile(this, uri)) { + Log.d(Config.LOGTAG, "trying to attach file that belonged to us"); + callback.error(R.string.security_error_invalid_file_access, null); + return; + } + final String compressPictures = getCompressPicturesPreference(); + if ("never".equals(compressPictures) + || ("auto".equals(compressPictures) && getFileBackend().useImageAsIs(uri))) { + Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + ": not compressing picture. sending as file"); + attachFileToConversation(conversation, uri, callback); + return; + } + final Message message; + if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { + message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED); + } else { + message = new Message(conversation, "", conversation.getNextEncryption()); + } + message.setCounterpart(conversation.getNextCounterpart()); + message.setType(Message.TYPE_IMAGE); + mFileAddingExecutor.execute(new Runnable() { + + @Override + public void run() { + try { + getFileBackend().copyImageToPrivateStorage(message, uri); + if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { + final PgpEngine pgpEngine = getPgpEngine(); + if (pgpEngine != null) { + pgpEngine.encrypt(message, callback); + } else if (callback != null) { + callback.error(R.string.unable_to_connect_to_keychain, null); + } + } else { + callback.success(message); + } + } catch (final FileBackend.FileCopyException e) { + callback.error(e.getResId(), message); + } + } + }); + } + + public void attachVideoToConversation(final Conversation conversation, final Uri uri, final UiCallback callback) { + File f = new File(FileUtils.getPath(this, uri)); + long filesize = f.length(); + String path = f.toString(); + final String compressVideos = getCompressVideoPreference(); + boolean sendVideoAsIs = false; + final Integer NOTIFICATION_ID = (int) (new Date().getTime() / 1000); + mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext()); + mBuilder.setContentTitle(getString(R.string.app_name)) + .setContentText(getString(R.string.compressing_video)) + .setSmallIcon(R.drawable.ic_play_box_outline_white_24dp) + .setOngoing(true) + .setProgress(0, 0, true); + mNotifyManager.notify(NOTIFICATION_ID, mBuilder.build()); + if (FileBackend.weOwnFile(this, uri)) { + Log.d(Config.LOGTAG, "trying to attach video that belonged to us"); + mNotifyManager.cancel(NOTIFICATION_ID); + callback.error(R.string.security_error_invalid_file_access, null); + return; + } + Log.d(Config.LOGTAG, "Video file (size) :" + f.toString() + "(" + filesize / 1024 / 1024 + "MB)"); + if (filesize == 0) { + Log.d(Config.LOGTAG, "Error with file, size = 0"); + mNotifyManager.cancel(NOTIFICATION_ID); + callback.error(R.string.error_file_corrupt, null); + return; + } + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US); + File compressed_file = new File(FileBackend.getConversationsVideoDirectory() + "/" + + dateFormat.format(new Date()) + + "_komp.mp4"); + final String compressed_path = compressed_file.toString(); + final Uri compressed_uri = Uri.fromFile(compressed_file); + if ("never".equals(compressVideos)) { + sendVideoAsIs = true; + } else if ("always".equals(compressVideos) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + sendVideoAsIs = false; + } else if ("auto".equals(compressVideos) && filesize > Config.VIDEO_MAX_SIZE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + sendVideoAsIs = false; + } else { + sendVideoAsIs = true; + } + if (!sendVideoAsIs) { + CompressVideo = new VideoCompressor(path, compressed_path, new Interface() { + @Override + public void videocompressed(boolean result) { + if (result) { + Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + ": sending compressed video."); + mNotifyManager.cancel(NOTIFICATION_ID); + attachFileToConversation(conversation, compressed_uri, callback); + } + } + }); + CompressVideo.execute(); + } else { + Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + ": not compressing video. sending as file"); + mNotifyManager.cancel(NOTIFICATION_ID); + attachFileToConversation(conversation, uri, callback); + } + } + + public interface Interface { + void videocompressed(boolean result); + } + + class VideoCompressor extends AsyncTask { + private String originalpath; + private String compressedpath; + private Interface mListener; + + VideoCompressor(String path, String compressed_path, Interface mListener) { + originalpath = path; + compressedpath = compressed_path; + this.mListener = mListener; + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + Log.d(Config.LOGTAG, "Start video compression"); + wakeLock.acquire(); + } + + @Override + protected Boolean doInBackground(String... params) { + return MediaController.getInstance().convertVideo(originalpath, compressedpath); + } + + @Override + protected void onPostExecute(Boolean compressed) { + super.onPostExecute(compressed); + wakeLock.release(); + File video = new File(compressedpath); + if (mListener != null) { + if (video.exists() && video.length() > 0) { + mListener.videocompressed(compressed); + Log.d(Config.LOGTAG, "Compression successfully!"); + } else { + mListener.videocompressed(false); + Log.d(Config.LOGTAG, "Compression failed!"); + } + } + } + } + + public Conversation find(Bookmark bookmark) { + return find(bookmark.getAccount(), bookmark.getJid()); + } + + public Conversation find(final Account account, final Jid jid) { + return find(getConversations(), account, jid); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + final String action = intent == null ? null : intent.getAction(); + String pushedAccountHash = null; + boolean interactive = false; + if (action != null) { + final Conversation c = findConversationByUuid(intent.getStringExtra("uuid")); + switch (action) { + case ConnectivityManager.CONNECTIVITY_ACTION: + if (hasInternetConnection() && Config.RESET_ATTEMPT_COUNT_ON_NETWORK_CHANGE) { + resetAllAttemptCounts(true); + } + break; + case ACTION_MERGE_PHONE_CONTACTS: + if (mRestoredFromDatabase) { + loadPhoneContacts(); + } + return START_STICKY; + case Intent.ACTION_SHUTDOWN: + logoutAndSave(true); + return START_NOT_STICKY; + case ACTION_CLEAR_NOTIFICATION: + if (c != null) { + mNotificationService.clear(c); + } else { + mNotificationService.clear(); + } + break; + case ACTION_DISMISS_ERROR_NOTIFICATIONS: + dismissErrorNotifications(); + break; + case ACTION_TRY_AGAIN: + resetAllAttemptCounts(false); + interactive = true; + break; + case ACTION_REPLY_TO_CONVERSATION: + Bundle remoteInput = RemoteInput.getResultsFromIntent(intent); + if (remoteInput != null && c != null) { + final CharSequence body = remoteInput.getCharSequence("text_reply"); + if (body != null && body.length() > 0) { + directReply(c, body.toString(), intent.getBooleanExtra("dismiss_notification", false)); + } + } + break; + case AudioManager.RINGER_MODE_CHANGED_ACTION: + if (xaOnSilentMode()) { + refreshAllPresences(); + } + break; + case Intent.ACTION_SCREEN_ON: + deactivateGracePeriod(); + case Intent.ACTION_SCREEN_OFF: + if (awayWhenScreenOff()) { + refreshAllPresences(); + } + break; + case ACTION_GCM_TOKEN_REFRESH: + refreshAllGcmTokens(); + break; + case ACTION_IDLE_PING: + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + scheduleNextIdlePing(); + } + break; + case ACTION_GCM_MESSAGE_RECEIVED: + Log.d(Config.LOGTAG, "gcm push message arrived in service. extras=" + intent.getExtras()); + pushedAccountHash = intent.getStringExtra("account"); + break; + } + } + this.wakeLock.acquire(); + + boolean pingNow = false; + HashSet pingCandidates = new HashSet<>(); + + for (Account account : accounts) { + if (!account.isOptionSet(Account.OPTION_DISABLED)) { + if (!hasInternetConnection()) { + account.setStatus(Account.State.NO_INTERNET); + if (statusListener != null) { + statusListener.onStatusChanged(account); + } + } else { + if (account.getStatus() == Account.State.NO_INTERNET) { + account.setStatus(Account.State.OFFLINE); + if (statusListener != null) { + statusListener.onStatusChanged(account); + } + } + if (account.getStatus() == Account.State.ONLINE) { + synchronized (mLowPingTimeoutMode) { + long lastReceived = account.getXmppConnection().getLastPacketReceived(); + long lastSent = account.getXmppConnection().getLastPingSent(); + long pingInterval = "ui".equals(action) ? Config.PING_MIN_INTERVAL * 1000 : Config.PING_MAX_INTERVAL * 1000; + long msToNextPing = (Math.max(lastReceived, lastSent) + pingInterval) - SystemClock.elapsedRealtime(); + int pingTimeout = mLowPingTimeoutMode.contains(account.getJid().toBareJid()) ? Config.LOW_PING_TIMEOUT * 1000 : Config.PING_TIMEOUT * 1000; + long pingTimeoutIn = (lastSent + pingTimeout) - SystemClock.elapsedRealtime(); + if (lastSent > lastReceived) { + if (pingTimeoutIn < 0) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": ping timeout"); + this.reconnectAccount(account, true, interactive); + } else { + int secs = (int) (pingTimeoutIn / 1000); + this.scheduleWakeUpCall(secs, account.getUuid().hashCode()); + } + } else { + pingCandidates.add(account); + if (CryptoHelper.getAccountFingerprint(account).equals(pushedAccountHash)) { + pingNow = true; + if (mLowPingTimeoutMode.add(account.getJid().toBareJid())) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": entering low ping timeout mode"); + } + } else if (msToNextPing <= 0) { + pingNow = true; + } else { + this.scheduleWakeUpCall((int) (msToNextPing / 1000), account.getUuid().hashCode()); + if (mLowPingTimeoutMode.remove(account.getJid().toBareJid())) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": leaving low ping timeout mode"); + } + } + } + } + } else if (account.getStatus() == Account.State.OFFLINE) { + reconnectAccount(account, true, interactive); + } else if (account.getStatus() == Account.State.CONNECTING) { + long secondsSinceLastConnect = (SystemClock.elapsedRealtime() - account.getXmppConnection().getLastConnect()) / 1000; + long secondsSinceLastDisco = (SystemClock.elapsedRealtime() - account.getXmppConnection().getLastDiscoStarted()) / 1000; + long discoTimeout = Config.CONNECT_DISCO_TIMEOUT - secondsSinceLastDisco; + long timeout = Config.CONNECT_TIMEOUT - secondsSinceLastConnect; + if (timeout < 0) { + Log.d(Config.LOGTAG, account.getJid() + ": time out during connect reconnecting"); + account.getXmppConnection().resetAttemptCount(); + reconnectAccount(account, true, interactive); + } else if (discoTimeout < 0) { + account.getXmppConnection().sendDiscoTimeout(); + scheduleWakeUpCall((int) Math.min(timeout, discoTimeout), account.getUuid().hashCode()); + } else { + scheduleWakeUpCall((int) Math.min(timeout, discoTimeout), account.getUuid().hashCode()); + } + } else { + if (account.getXmppConnection().getTimeToNextAttempt() <= 0) { + reconnectAccount(account, true, interactive); + } + } + } + if (mOnAccountUpdate != null) { + mOnAccountUpdate.onAccountUpdate(); + } + } + } + if (pingNow) { + for (Account account : pingCandidates) { + synchronized (mLowPingTimeoutMode) { + final boolean lowTimeout = mLowPingTimeoutMode.contains(account.getJid().toBareJid()); + account.getXmppConnection().sendPing(); + Log.d(Config.LOGTAG, account.getJid().toBareJid() + " send ping (action=" + action + ",lowTimeout=" + Boolean.toString(lowTimeout) + ")"); + scheduleWakeUpCall(lowTimeout ? Config.LOW_PING_TIMEOUT : Config.PING_TIMEOUT, account.getUuid().hashCode()); + } + } + } + if (wakeLock.isHeld()) { + try { + wakeLock.release(); + } catch (final RuntimeException ignored) { + } + } + return START_STICKY; + } + + public boolean isDataSaverDisabled() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); + return !connectivityManager.isActiveNetworkMetered() + || connectivityManager.getRestrictBackgroundStatus() == ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED; + } else { + return true; + } + } + + private void directReply(Conversation conversation, String body, final boolean dismissAfterReply) { + Message message = new Message(conversation, body, conversation.getNextEncryption()); + message.markUnread(); + if (message.getEncryption() == Message.ENCRYPTION_PGP) { + getPgpEngine().encrypt(message, new UiCallback() { + @Override + public void success(Message message) { + message.setEncryption(Message.ENCRYPTION_DECRYPTED); + sendMessage(message); + if (dismissAfterReply) { + markRead(message.getConversation(), true); + } else { + mNotificationService.pushFromDirectReply(message); + } + } + + @Override + public void error(int errorCode, Message object) { + + } + + @Override + public void userInputRequried(PendingIntent pi, Message object) { + + } + }); + } else { + sendMessage(message); + if (dismissAfterReply) { + markRead(conversation, true); + } else { + mNotificationService.pushFromDirectReply(message); + } + } + } + + private boolean xaOnSilentMode() { + return getPreferences().getBoolean("xa_on_silent_mode", false); + } + + private boolean manuallyChangePresence() { + return getPreferences().getBoolean("manually_change_presence", false); + } + + private boolean treatVibrateAsSilent() { + return getPreferences().getBoolean("treat_vibrate_as_silent", false); + } + + private boolean awayWhenScreenOff() { + return getPreferences().getBoolean("away_when_screen_off", false); + } + + private String getCompressPicturesPreference() { + return getPreferences().getString("picture_compression", "auto"); + } + + private String getCompressVideoPreference() { + return getPreferences().getString("video_compression", "auto"); + } + + private Presence.Status getTargetPresence() { + if (xaOnSilentMode() && isPhoneSilenced()) { + return Presence.Status.XA; + } else if (awayWhenScreenOff() && !isInteractive()) { + return Presence.Status.AWAY; + } else { + return Presence.Status.ONLINE; + } + } + + @SuppressLint("NewApi") + @SuppressWarnings("deprecation") + public boolean isInteractive() { + final PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + + final boolean isScreenOn; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + isScreenOn = pm.isScreenOn(); + } else { + isScreenOn = pm.isInteractive(); + } + return isScreenOn; + } + + private boolean isPhoneSilenced() { + AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + try { + if (treatVibrateAsSilent()) { + return audioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL; + } else { + return audioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT; + } + } catch (Throwable throwable) { + Log.d(Config.LOGTAG, "platform bug in isPhoneSilenced (" + throwable.getMessage() + ")"); + return false; + } + } + + private void resetAllAttemptCounts(boolean reallyAll) { + Log.d(Config.LOGTAG, "resetting all attempt counts"); + for (Account account : accounts) { + if (account.hasErrorStatus() || reallyAll) { + final XmppConnection connection = account.getXmppConnection(); + if (connection != null) { + connection.resetAttemptCount(); + } + } + if (account.setShowErrorNotification(true)) { + databaseBackend.updateAccount(account); + } + } + mNotificationService.updateErrorNotification(); + } + + private void dismissErrorNotifications() { + for (final Account account : this.accounts) { + if (account.hasErrorStatus()) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": dismissing error notification"); + if (account.setShowErrorNotification(false)) { + databaseBackend.updateAccount(account); + } + } + } + } + + public boolean hasInternetConnection() { + ConnectivityManager cm = (ConnectivityManager) getApplicationContext() + .getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); + return activeNetwork != null && activeNetwork.isConnected(); + } + + public boolean isWIFI() { + ConnectivityManager cm = (ConnectivityManager) getApplicationContext() + .getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); + if (activeNetwork != null) { // connected to the internet + if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) { + return true; + } + } + return false; + } + + public boolean isMobile() { + ConnectivityManager cm = (ConnectivityManager) getApplicationContext() + .getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); + if (activeNetwork != null) { // connected to the internet + if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) { + if (!activeNetwork.isRoaming()) { + return true; + } + } + } + return false; + } + + public boolean isMobileRoaming() { + ConnectivityManager cm = (ConnectivityManager) getApplicationContext() + .getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); + if (activeNetwork != null) { // connected to the internet + if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) { + if (activeNetwork.isRoaming()) { + return true; + } + } + } + return false; + } + + @SuppressLint("TrulyRandom") + @Override + public void onCreate() { + ExceptionHelper.init(getApplicationContext()); + PRNGFixes.apply(); + this.mRandom = new SecureRandom(); + updateMemorizingTrustmanager(); + final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); + final int cacheSize = maxMemory / 8; + this.mBitmapCache = new LruCache(cacheSize) { + @Override + protected int sizeOf(final String key, final Bitmap bitmap) { + return bitmap.getByteCount() / 1024; + } + }; + + this.databaseBackend = DatabaseBackend.getInstance(getApplicationContext()); + this.accounts = databaseBackend.getAccounts(); + + if (databaseBackend.startTimeCountExceedsThreshold()) { + Log.d(Config.LOGTAG, "number of restarts exceeds threshold."); + } + + restoreFromDatabase(); + + getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, contactObserver); + new Thread(new Runnable() { + @Override + public void run() { + fileObserver.startWatching(); + } + }).start(); + if (Config.supportOpenPgp()) { + this.pgpServiceConnection = new OpenPgpServiceConnection(getApplicationContext(), "org.sufficientlysecure.keychain", new OpenPgpServiceConnection.OnBound() { + @Override + public void onBound(IOpenPgpService2 service) { + for (Account account : accounts) { + final PgpDecryptionService pgp = account.getPgpDecryptionService(); + if (pgp != null) { + pgp.continueDecryption(true); + } + } + } + + @Override + public void onError(Exception e) { + } + }); + this.pgpServiceConnection.bindToService(); + } + + this.pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "XmppConnectionService"); + toggleForegroundService(); + updateUnreadCountBadge(); + toggleScreenEventReceiver(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + scheduleNextIdlePing(); + } + //start export log service every day at given time + ScheduleAutomaticExport(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + registerReceiver(this.mEventReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); + } + } + + @Override + public void onTrimMemory(int level) { + super.onTrimMemory(level); + if (level >= TRIM_MEMORY_COMPLETE) { + Log.d(Config.LOGTAG, "clear cache due to low memory"); + getBitmapCache().evictAll(); + } + } + + @Override + public void onDestroy() { + try { + unregisterReceiver(this.mEventReceiver); + } catch (IllegalArgumentException e) { + //ignored + } + fileObserver.stopWatching(); + super.onDestroy(); + // cancel scheduled exporter + CancelAutomaticExport(); + } + + public void toggleScreenEventReceiver() { + if (awayWhenScreenOff() && !manuallyChangePresence()) { + final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON); + filter.addAction(Intent.ACTION_SCREEN_OFF); + registerReceiver(this.mEventReceiver, filter); + } else { + try { + unregisterReceiver(this.mEventReceiver); + } catch (IllegalArgumentException e) { + //ignored + } + } + } + + public void toggleForegroundService() { + if (Config.USE_ALWAYS_FOREGROUND) { + startForeground(NotificationService.FOREGROUND_NOTIFICATION_ID, this.mNotificationService.createForegroundNotification()); + } else { + stopForeground(true); + } + } + + @Override + public void onTaskRemoved(final Intent rootIntent) { + super.onTaskRemoved(rootIntent); + } + + private void logoutAndSave(boolean stop) { + int activeAccounts = 0; + databaseBackend.clearStartTimeCounter(); // regular swipes don't count towards restart counter + for (final Account account : accounts) { + if (account.getStatus() != Account.State.DISABLED) { + activeAccounts++; + } + databaseBackend.writeRoster(account.getRoster()); + if (account.getXmppConnection() != null) { + new Thread(new Runnable() { + @Override + public void run() { + disconnect(account, false); + } + }).start(); + } + } + if (stop || activeAccounts == 0) { + Log.d(Config.LOGTAG, "good bye"); + stopSelf(); + } + } + + public void scheduleWakeUpCall(int seconds, int requestCode) { + final long timeToWake = SystemClock.elapsedRealtime() + (seconds < 0 ? 1 : seconds + 1) * 1000; + AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); + Intent intent = new Intent(this, EventReceiver.class); + intent.setAction("ping"); + PendingIntent alarmIntent = PendingIntent.getBroadcast(this, requestCode, intent, 0); + alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, timeToWake, alarmIntent); + } + + @TargetApi(Build.VERSION_CODES.M) + private void scheduleNextIdlePing() { + Log.d(Config.LOGTAG, "schedule next idle ping"); + AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); + Intent intent = new Intent(this, EventReceiver.class); + intent.setAction(ACTION_IDLE_PING); + alarmManager.setAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, + SystemClock.elapsedRealtime() + (Config.IDLE_PING_INTERVAL * 1000), + PendingIntent.getBroadcast(this, 0, intent, 0) + ); + } + + public XmppConnection createConnection(final Account account) { + final SharedPreferences sharedPref = getPreferences(); + String resource; + try { + resource = sharedPref.getString("resource", getString(R.string.default_resource)).toLowerCase(Locale.ENGLISH); + if (resource.trim().isEmpty()) { + throw new Exception(); + } + } catch (Exception e) { + resource = "Pix-Art Messenger"; + } + account.setResource(resource); + final XmppConnection connection = new XmppConnection(account, this); + connection.setOnMessagePacketReceivedListener(this.mMessageParser); + connection.setOnStatusChangedListener(this.statusListener); + connection.setOnPresencePacketReceivedListener(this.mPresenceParser); + connection.setOnUnregisteredIqPacketReceivedListener(this.mIqParser); + connection.setOnJinglePacketReceivedListener(this.jingleListener); + connection.setOnBindListener(this.mOnBindListener); + connection.setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener); + connection.addOnAdvancedStreamFeaturesAvailableListener(this.mMessageArchiveService); + connection.addOnAdvancedStreamFeaturesAvailableListener(this.mAvatarService); + AxolotlService axolotlService = account.getAxolotlService(); + if (axolotlService != null) { + connection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService); + } + return connection; + } + + public void sendChatState(Conversation conversation) { + if (sendChatStates()) { + MessagePacket packet = mMessageGenerator.generateChatState(conversation); + sendMessagePacket(conversation.getAccount(), packet); + } + } + + private void sendFileMessage(final Message message, final boolean delay) { + Log.d(Config.LOGTAG, "send file message"); + final Account account = message.getConversation().getAccount(); + if (account.httpUploadAvailable(fileBackend.getFile(message, false).getSize())) { + mHttpConnectionManager.createNewUploadConnection(message, delay); + } else { + mJingleConnectionManager.createNewConnection(message); + } + } + + public void sendMessage(final Message message) { + sendMessage(message, false, false); + } + + private void sendMessage(final Message message, final boolean resend, final boolean delay) { + final Account account = message.getConversation().getAccount(); + if (account.setShowErrorNotification(true)) { + databaseBackend.updateAccount(account); + mNotificationService.updateErrorNotification(); + } + final Conversation conversation = message.getConversation(); + account.deactivateGracePeriod(); + MessagePacket packet = null; + final boolean addToConversation = (conversation.getMode() != Conversation.MODE_MULTI + || account.getServerIdentity() != XmppConnection.Identity.SLACK) + && !message.edited(); + boolean saveInDb = addToConversation; + message.setStatus(Message.STATUS_WAITING); + + if (!resend && message.getEncryption() != Message.ENCRYPTION_OTR) { + message.getConversation().endOtrIfNeeded(); + message.getConversation().findUnsentMessagesWithEncryption(Message.ENCRYPTION_OTR, + new Conversation.OnMessageFound() { + @Override + public void onMessageFound(Message message) { + markMessage(message, Message.STATUS_SEND_FAILED); + } + }); + } + + if (account.isOnlineAndConnected()) { + switch (message.getEncryption()) { + case Message.ENCRYPTION_NONE: + if (message.needsUploading()) { + if (account.httpUploadAvailable(fileBackend.getFile(message, false).getSize()) + || message.fixCounterpart()) { + this.sendFileMessage(message, delay); + } else { + break; + } + } else { + packet = mMessageGenerator.generateChat(message); + } + break; + case Message.ENCRYPTION_PGP: + case Message.ENCRYPTION_DECRYPTED: + if (message.needsUploading()) { + if (account.httpUploadAvailable(fileBackend.getFile(message, false).getSize()) + || message.fixCounterpart()) { + this.sendFileMessage(message, delay); + } else { + break; + } + } else { + packet = mMessageGenerator.generatePgpChat(message); + } + break; + case Message.ENCRYPTION_OTR: + SessionImpl otrSession = conversation.getOtrSession(); + if (otrSession != null && otrSession.getSessionStatus() == SessionStatus.ENCRYPTED) { + try { + message.setCounterpart(Jid.fromSessionID(otrSession.getSessionID())); + } catch (InvalidJidException e) { + break; + } + if (message.needsUploading()) { + mJingleConnectionManager.createNewConnection(message); + } else { + packet = mMessageGenerator.generateOtrChat(message); + } + } else if (otrSession == null) { + if (message.fixCounterpart()) { + conversation.startOtrSession(message.getCounterpart().getResourcepart(), true); + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not fix counterpart for OTR message to contact " + message.getContact().getJid()); + break; + } + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + " OTR session with " + message.getContact() + " is in wrong state: " + otrSession.getSessionStatus().toString()); + } + break; + case Message.ENCRYPTION_AXOLOTL: + message.setFingerprint(account.getAxolotlService().getOwnFingerprint()); + if (message.needsUploading()) { + if (account.httpUploadAvailable(fileBackend.getFile(message, false).getSize()) + || message.fixCounterpart()) { + this.sendFileMessage(message, delay); + } else { + break; + } + } else { + XmppAxolotlMessage axolotlMessage = account.getAxolotlService().fetchAxolotlMessageFromCache(message); + if (axolotlMessage == null) { + account.getAxolotlService().preparePayloadMessage(message, delay); + } else { + packet = mMessageGenerator.generateAxolotlChat(message, axolotlMessage); + } + } + break; + + } + if (packet != null) { + if (account.getXmppConnection().getFeatures().sm() || (conversation.getMode() == Conversation.MODE_MULTI && message.getCounterpart().isBareJid())) { + message.setStatus(Message.STATUS_UNSEND); + } else { + message.setStatus(Message.STATUS_SEND); + } + } + } else { + switch (message.getEncryption()) { + case Message.ENCRYPTION_DECRYPTED: + if (!message.needsUploading()) { + String pgpBody = message.getEncryptedBody(); + String decryptedBody = message.getBody(); + message.setBody(pgpBody); + message.setEncryption(Message.ENCRYPTION_PGP); + if (message.edited()) { + message.setBody(decryptedBody); + message.setEncryption(Message.ENCRYPTION_DECRYPTED); + databaseBackend.updateMessage(message, message.getEditedId()); + updateConversationUi(); + return; + } else { + databaseBackend.createMessage(message); + saveInDb = false; + message.setBody(decryptedBody); + message.setEncryption(Message.ENCRYPTION_DECRYPTED); + } + } + break; + case Message.ENCRYPTION_OTR: + if (!conversation.hasValidOtrSession() && message.getCounterpart() != null) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": create otr session without starting for " + message.getContact().getJid()); + conversation.startOtrSession(message.getCounterpart().getResourcepart(), false); + } + break; + case Message.ENCRYPTION_AXOLOTL: + message.setFingerprint(account.getAxolotlService().getOwnFingerprint()); + break; + } + } + + if (resend) { + if (packet != null && addToConversation) { + if (account.getXmppConnection().getFeatures().sm() || (conversation.getMode() == Conversation.MODE_MULTI && message.getCounterpart().isBareJid())) { + markMessage(message, Message.STATUS_UNSEND); + } else { + markMessage(message, Message.STATUS_SEND); + } + } + } else { + if (addToConversation) { + conversation.add(message); + } + if (message.getEncryption() == Message.ENCRYPTION_NONE || saveEncryptedMessages()) { + if (saveInDb) { + databaseBackend.createMessage(message); + } else if (message.edited()) { + databaseBackend.updateMessage(message, message.getEditedId()); + } + } + updateConversationUi(); + } + if (packet != null) { + if (delay) { + mMessageGenerator.addDelay(packet, message.getTimeSent()); + } + if (conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) { + if (this.sendChatStates()) { + packet.addChild(ChatState.toElement(conversation.getOutgoingChatState())); + } + } + sendMessagePacket(account, packet); + } + } + + private void sendUnsentMessages(final Conversation conversation) { + conversation.findWaitingMessages(new Conversation.OnMessageFound() { + + @Override + public void onMessageFound(Message message) { + resendMessage(message, true); + } + }); + } + + public void resendMessage(final Message message, final boolean delay) { + sendMessage(message, true, delay); + } + + public void fetchRosterFromServer(final Account account) { + final IqPacket iqPacket = new IqPacket(IqPacket.TYPE.GET); + if (!"".equals(account.getRosterVersion())) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + + ": fetching roster version " + account.getRosterVersion()); + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": fetching roster"); + } + iqPacket.query(Xmlns.ROSTER).setAttribute("ver", account.getRosterVersion()); + sendIqPacket(account, iqPacket, mIqParser); + } + + public void fetchBookmarks(final Account account) { + final IqPacket iqPacket = new IqPacket(IqPacket.TYPE.GET); + final Element query = iqPacket.query("jabber:iq:private"); + query.addChild("storage", "storage:bookmarks"); + final OnIqPacketReceived callback = new OnIqPacketReceived() { + + @Override + public void onIqPacketReceived(final Account account, final IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.RESULT) { + final Element query = packet.query(); + final HashMap 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")) { + final Bookmark bookmark = Bookmark.parse(item, account); + Bookmark old = bookmarks.put(bookmark.getJid(), bookmark); + if (old != null && old.getBookmarkName() != null && bookmark.getBookmarkName() == null) { + bookmark.setBookmarkName(old.getBookmarkName()); + } + Conversation conversation = find(bookmark); + if (conversation != null) { + conversation.setBookmark(bookmark); + } else if (bookmark.autojoin() && bookmark.getJid() != null && autojoin) { + conversation = findOrCreateConversation( + account, bookmark.getJid(), true); + conversation.setBookmark(bookmark); + joinMuc(conversation); + } + } + } + } + account.setBookmarks(new ArrayList<>(bookmarks.values())); + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not fetch bookmarks"); + } + } + }; + sendIqPacket(account, iqPacket, callback); + } + + public void pushBookmarks(Account account) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": pushing bookmarks"); + IqPacket iqPacket = new IqPacket(IqPacket.TYPE.SET); + Element query = iqPacket.query("jabber:iq:private"); + Element storage = query.addChild("storage", "storage:bookmarks"); + for (Bookmark bookmark : account.getBookmarks()) { + storage.addChild(bookmark); + } + sendIqPacket(account, iqPacket, mDefaultIqHandler); + } + + private void restoreFromDatabase() { + synchronized (this.conversations) { + final Map accountLookupTable = new Hashtable<>(); + for (Account account : this.accounts) { + accountLookupTable.put(account.getUuid(), account); + } + this.conversations.addAll(databaseBackend.getConversations(Conversation.STATUS_AVAILABLE)); + for (Conversation conversation : this.conversations) { + Account account = accountLookupTable.get(conversation.getAccountUuid()); + conversation.setAccount(account); + } + Runnable runnable = new Runnable() { + @Override + public void run() { + Log.d(Config.LOGTAG, "restoring roster"); + for (Account account : accounts) { + databaseBackend.readRoster(account.getRoster()); + account.initAccountServices(XmppConnectionService.this); //roster needs to be loaded at this stage + } + getBitmapCache().evictAll(); + loadPhoneContacts(); + Log.d(Config.LOGTAG, "restoring messages"); + for (Conversation conversation : conversations) { + conversation.addAll(0, databaseBackend.getMessages(conversation, Config.PAGE_SIZE)); + checkDeletedFiles(conversation); + conversation.findUnsentTextMessages(new Conversation.OnMessageFound() { + + @Override + public void onMessageFound(Message message) { + markMessage(message, Message.STATUS_WAITING); + } + }); + conversation.findUnreadMessages(new Conversation.OnMessageFound() { + @Override + public void onMessageFound(Message message) { + mNotificationService.pushFromBacklog(message); + } + }); + } + mNotificationService.finishBacklog(false); + mRestoredFromDatabase = true; + Log.d(Config.LOGTAG, "restored all messages"); + updateConversationUi(); + } + }; + mDatabaseExecutor.execute(runnable); + } + } + + public void loadPhoneContacts() { + mContactMergerExecutor.execute(new Runnable() { + @Override + public void run() { + PhoneHelper.loadPhoneContacts(XmppConnectionService.this, new OnPhoneContactsLoadedListener() { + @Override + public void onPhoneContactsLoaded(List phoneContacts) { + Log.d(Config.LOGTAG, "start merging phone contacts with roster"); + for (Account account : accounts) { + List withSystemAccounts = account.getRoster().getWithSystemAccounts(); + for (Bundle phoneContact : phoneContacts) { + Jid jid; + try { + jid = Jid.fromString(phoneContact.getString("jid")); + } catch (final InvalidJidException e) { + continue; + } + final Contact contact = account.getRoster().getContact(jid); + String systemAccount = phoneContact.getInt("phoneid") + + "#" + + phoneContact.getString("lookup"); + contact.setSystemAccount(systemAccount); + if (contact.setPhotoUri(phoneContact.getString("photouri"))) { + getAvatarService().clear(contact); + } + contact.setSystemName(phoneContact.getString("displayname")); + withSystemAccounts.remove(contact); + } + for (Contact contact : withSystemAccounts) { + contact.setSystemAccount(null); + contact.setSystemName(null); + if (contact.setPhotoUri(null)) { + getAvatarService().clear(contact); + } + } + } + Log.d(Config.LOGTAG, "finished merging phone contacts"); + updateAccountUi(); + } + }); + } + }); + } + + public List getConversations() { + return this.conversations; + } + + private void checkDeletedFiles(Conversation conversation) { + conversation.findMessagesWithFiles(new Conversation.OnMessageFound() { + + @Override + public void onMessageFound(Message message) { + if (!getFileBackend().isFileAvailable(message)) { + message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); + final int s = message.getStatus(); + if (s == Message.STATUS_WAITING || s == Message.STATUS_OFFERED || s == Message.STATUS_UNSEND) { + markMessage(message, Message.STATUS_SEND_FAILED); + } + } + } + }); + } + + private void markFileDeleted(final String path) { + Log.d(Config.LOGTAG, "deleted file " + path); + for (Conversation conversation : getConversations()) { + conversation.findMessagesWithFiles(new Conversation.OnMessageFound() { + @Override + public void onMessageFound(Message message) { + DownloadableFile file = fileBackend.getFile(message); + if (file.getAbsolutePath().equals(path)) { + if (!file.exists()) { + message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); + final int s = message.getStatus(); + if (s == Message.STATUS_WAITING || s == Message.STATUS_OFFERED || s == Message.STATUS_UNSEND) { + markMessage(message, Message.STATUS_SEND_FAILED); + } else { + updateConversationUi(); + } + } else { + Log.d(Config.LOGTAG, "found matching message for file " + path + " but file still exists"); + } + } + } + }); + } + } + + public void populateWithOrderedConversations(final List list) { + populateWithOrderedConversations(list, true); + } + + public void populateWithOrderedConversations(final List list, boolean includeNoFileUpload) { + list.clear(); + if (includeNoFileUpload) { + list.addAll(getConversations()); + } else { + for (Conversation conversation : getConversations()) { + if (conversation.getMode() == Conversation.MODE_SINGLE + || conversation.getAccount().httpUploadAvailable()) { + list.add(conversation); + } + } + } + try { + Collections.sort(list); + } catch (IllegalArgumentException e) { + //ignore + } + } + + public void loadMoreMessages(final Conversation conversation, final long timestamp, final OnMoreMessagesLoaded callback) { + if (XmppConnectionService.this.getMessageArchiveService().queryInProgress(conversation, callback)) { + return; + } else if (timestamp == 0) { + return; + } + Log.d(Config.LOGTAG, "load more messages for " + conversation.getName() + " prior to " + MessageGenerator.getTimestamp(timestamp)); + Runnable runnable = new Runnable() { + @Override + public void run() { + final Account account = conversation.getAccount(); + List messages = databaseBackend.getMessages(conversation, 50, timestamp); + if (messages.size() > 0) { + conversation.addAll(0, messages); + checkDeletedFiles(conversation); + callback.onMoreMessagesLoaded(messages.size(), conversation); + } else if (conversation.hasMessagesLeftOnServer() + && account.isOnlineAndConnected() + && conversation.getLastClearHistory() == 0) { + if ((conversation.getMode() == Conversation.MODE_SINGLE && account.getXmppConnection().getFeatures().mam()) + || (conversation.getMode() == Conversation.MODE_MULTI && conversation.getMucOptions().mamSupport())) { + MessageArchiveService.Query query = getMessageArchiveService().query(conversation, 0, timestamp); + if (query != null) { + query.setCallback(callback); + } + callback.informUser(R.string.fetching_history_from_server); + } + } + } + }; + mDatabaseExecutor.execute(runnable); + } + + public List getAccounts() { + return this.accounts; + } + + public List findAllConferencesWith(Contact contact) { + ArrayList results = new ArrayList<>(); + for (Conversation conversation : conversations) { + if (conversation.getMode() == Conversation.MODE_MULTI + && conversation.getMucOptions().isContactInRoom(contact)) { + results.add(conversation); + } + } + return results; + } + + public Conversation find(final Iterable haystack, final Contact contact) { + for (final Conversation conversation : haystack) { + if (conversation.getContact() == contact) { + return conversation; + } + } + return null; + } + + public Conversation find(final Iterable haystack, final Account account, final Jid jid) { + if (jid == null) { + return null; + } + for (final Conversation conversation : haystack) { + if ((account == null || conversation.getAccount() == account) + && (conversation.getJid().toBareJid().equals(jid.toBareJid()))) { + return conversation; + } + } + return null; + } + + public Conversation findOrCreateConversation(final Account account, final Jid jid, final boolean muc) { + return this.findOrCreateConversation(account, jid, muc, null); + } + + public Conversation findOrCreateConversation(final Account account, final Jid jid, final boolean muc, final MessageArchiveService.Query query) { + synchronized (this.conversations) { + Conversation conversation = find(account, jid); + if (conversation != null) { + return conversation; + } + conversation = databaseBackend.findConversation(account, jid); + if (conversation != null) { + conversation.setStatus(Conversation.STATUS_AVAILABLE); + conversation.setAccount(account); + if (muc) { + conversation.setMode(Conversation.MODE_MULTI); + conversation.setContactJid(jid); + } else { + conversation.setMode(Conversation.MODE_SINGLE); + conversation.setContactJid(jid.toBareJid()); + } + conversation.addAll(0, databaseBackend.getMessages(conversation, Config.PAGE_SIZE)); + this.databaseBackend.updateConversation(conversation); + } else { + String conversationName; + Contact contact = account.getRoster().getContact(jid); + if (contact != null) { + conversationName = contact.getDisplayName(); + } else { + conversationName = jid.getLocalpart(); + } + if (muc) { + conversation = new Conversation(conversationName, account, jid, + Conversation.MODE_MULTI); + } else { + conversation = new Conversation(conversationName, account, jid.toBareJid(), + Conversation.MODE_SINGLE); + } + this.databaseBackend.createConversation(conversation); + } + if (account.getXmppConnection() != null + && account.getXmppConnection().getFeatures().mam() + && !muc) { + if (query == null) { + this.mMessageArchiveService.query(conversation); + } else { + if (query.getConversation() == null) { + this.mMessageArchiveService.query(conversation, query.getStart()); + } + } + } + checkDeletedFiles(conversation); + this.conversations.add(conversation); + updateConversationUi(); + return conversation; + } + } + + public void archiveConversation(Conversation conversation) { + getNotificationService().clear(conversation); + conversation.setStatus(Conversation.STATUS_ARCHIVED); + synchronized (this.conversations) { + if (conversation.getMode() == Conversation.MODE_MULTI) { + if (conversation.getAccount().getStatus() == Account.State.ONLINE) { + Bookmark bookmark = conversation.getBookmark(); + if (bookmark != null && bookmark.autojoin() && respectAutojoin()) { + bookmark.setAutojoin(false); + pushBookmarks(bookmark.getAccount()); + } + } + leaveMuc(conversation); + } else { + conversation.endOtrIfNeeded(); + if (conversation.getContact().getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { + Log.d(Config.LOGTAG, "Canceling presence request from " + conversation.getJid().toString()); + sendPresencePacket( + conversation.getAccount(), + mPresenceGenerator.stopPresenceUpdatesTo(conversation.getContact()) + ); + } + } + updateConversation(conversation); + this.conversations.remove(conversation); + updateConversationUi(); + } + } + + public void createAccount(final Account account) { + account.initAccountServices(this); + databaseBackend.createAccount(account); + this.accounts.add(account); + this.reconnectAccountInBackground(account); + updateAccountUi(); + } + + public void createAccountFromKey(final String alias, final OnAccountCreated callback) { + new Thread(new Runnable() { + @Override + public void run() { + try { + X509Certificate[] chain = KeyChain.getCertificateChain(XmppConnectionService.this, alias); + Pair info = CryptoHelper.extractJidAndName(chain[0]); + if (findAccountByJid(info.first) == null) { + Account account = new Account(info.first, ""); + account.setPrivateKeyAlias(alias); + account.setOption(Account.OPTION_DISABLED, true); + account.setDisplayName(info.second); + createAccount(account); + callback.onAccountCreated(account); + if (Config.X509_VERIFICATION) { + try { + getMemorizingTrustManager().getNonInteractive().checkClientTrusted(chain, "RSA"); + } catch (CertificateException e) { + callback.informUser(R.string.certificate_chain_is_not_trusted); + } + } + } else { + callback.informUser(R.string.account_already_exists); + } + } catch (Exception e) { + e.printStackTrace(); + callback.informUser(R.string.unable_to_parse_certificate); + } + } + }).start(); + + } + + public void updateKeyInAccount(final Account account, final String alias) { + Log.d(Config.LOGTAG, "update key in account " + alias); + try { + X509Certificate[] chain = KeyChain.getCertificateChain(XmppConnectionService.this, alias); + Pair info = CryptoHelper.extractJidAndName(chain[0]); + if (account.getJid().toBareJid().equals(info.first)) { + account.setPrivateKeyAlias(alias); + account.setDisplayName(info.second); + databaseBackend.updateAccount(account); + if (Config.X509_VERIFICATION) { + try { + getMemorizingTrustManager().getNonInteractive().checkClientTrusted(chain, "RSA"); + } catch (CertificateException e) { + showErrorToastInUi(R.string.certificate_chain_is_not_trusted); + } + account.getAxolotlService().regenerateKeys(true); + } + } else { + showErrorToastInUi(R.string.jid_does_not_match_certificate); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public boolean updateAccount(final Account account) { + if (databaseBackend.updateAccount(account)) { + account.setShowErrorNotification(true); + this.statusListener.onStatusChanged(account); + databaseBackend.updateAccount(account); + reconnectAccountInBackground(account); + updateAccountUi(); + getNotificationService().updateErrorNotification(); + return true; + } else { + return false; + } + } + + public void updateAccountPasswordOnServer(final Account account, final String newPassword, final OnAccountPasswordChanged callback) { + final IqPacket iq = getIqGenerator().generateSetPassword(account, newPassword); + sendIqPacket(account, iq, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(final Account account, final IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.RESULT) { + account.setPassword(newPassword); + account.setOption(Account.OPTION_MAGIC_CREATE, false); + databaseBackend.updateAccount(account); + callback.onPasswordChangeSucceeded(); + } else { + callback.onPasswordChangeFailed(); + } + } + }); + } + + public void deleteAccount(final Account account) { + synchronized (this.conversations) { + for (final Conversation conversation : conversations) { + if (conversation.getAccount() == account) { + if (conversation.getMode() == Conversation.MODE_MULTI) { + leaveMuc(conversation); + } else if (conversation.getMode() == Conversation.MODE_SINGLE) { + conversation.endOtrIfNeeded(); + } + conversations.remove(conversation); + } + } + if (account.getXmppConnection() != null) { + new Thread(new Runnable() { + @Override + public void run() { + disconnect(account, true); + } + }).start(); + } + Runnable runnable = new Runnable() { + @Override + public void run() { + if (!databaseBackend.deleteAccount(account)) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": unable to delete account"); + } + } + }; + mDatabaseExecutor.execute(runnable); + this.accounts.remove(account); + updateAccountUi(); + getNotificationService().updateErrorNotification(); + } + } + + public void setOnConversationListChangedListener(OnConversationUpdate listener) { + synchronized (this) { + this.mLastActivity = System.currentTimeMillis(); + if (checkListeners()) { + switchToForeground(); + } + this.mOnConversationUpdate = listener; + this.mNotificationService.setIsInForeground(true); + if (this.convChangedListenerCount < 2) { + this.convChangedListenerCount++; + } + } + } + + public void removeOnConversationListChangedListener() { + synchronized (this) { + this.convChangedListenerCount--; + if (this.convChangedListenerCount <= 0) { + this.convChangedListenerCount = 0; + this.mOnConversationUpdate = null; + this.mNotificationService.setIsInForeground(false); + if (checkListeners()) { + switchToBackground(); + } + } + } + } + + public void setOnShowErrorToastListener(OnShowErrorToast onShowErrorToast) { + synchronized (this) { + if (checkListeners()) { + switchToForeground(); + } + this.mOnShowErrorToast = onShowErrorToast; + if (this.showErrorToastListenerCount < 2) { + this.showErrorToastListenerCount++; + } + } + this.mOnShowErrorToast = onShowErrorToast; + } + + public void removeOnShowErrorToastListener() { + synchronized (this) { + this.showErrorToastListenerCount--; + if (this.showErrorToastListenerCount <= 0) { + this.showErrorToastListenerCount = 0; + this.mOnShowErrorToast = null; + if (checkListeners()) { + switchToBackground(); + } + } + } + } + + public void setOnAccountListChangedListener(OnAccountUpdate listener) { + synchronized (this) { + if (checkListeners()) { + switchToForeground(); + } + this.mOnAccountUpdate = listener; + if (this.accountChangedListenerCount < 2) { + this.accountChangedListenerCount++; + } + } + } + + public void removeOnAccountListChangedListener() { + synchronized (this) { + this.accountChangedListenerCount--; + if (this.accountChangedListenerCount <= 0) { + this.mOnAccountUpdate = null; + this.accountChangedListenerCount = 0; + if (checkListeners()) { + switchToBackground(); + } + } + } + } + + public void setOnCaptchaRequestedListener(OnCaptchaRequested listener) { + synchronized (this) { + if (checkListeners()) { + switchToForeground(); + } + this.mOnCaptchaRequested = listener; + if (this.captchaRequestedListenerCount < 2) { + this.captchaRequestedListenerCount++; + } + } + } + + public void removeOnCaptchaRequestedListener() { + synchronized (this) { + this.captchaRequestedListenerCount--; + if (this.captchaRequestedListenerCount <= 0) { + this.mOnCaptchaRequested = null; + this.captchaRequestedListenerCount = 0; + if (checkListeners()) { + switchToBackground(); + } + } + } + } + + public void setOnRosterUpdateListener(final OnRosterUpdate listener) { + synchronized (this) { + if (checkListeners()) { + switchToForeground(); + } + this.mOnRosterUpdate = listener; + if (this.rosterChangedListenerCount < 2) { + this.rosterChangedListenerCount++; + } + } + } + + public void removeOnRosterUpdateListener() { + synchronized (this) { + this.rosterChangedListenerCount--; + if (this.rosterChangedListenerCount <= 0) { + this.rosterChangedListenerCount = 0; + this.mOnRosterUpdate = null; + if (checkListeners()) { + switchToBackground(); + } + } + } + } + + public void setOnUpdateBlocklistListener(final OnUpdateBlocklist listener) { + synchronized (this) { + if (checkListeners()) { + switchToForeground(); + } + this.mOnUpdateBlocklist = listener; + if (this.updateBlocklistListenerCount < 2) { + this.updateBlocklistListenerCount++; + } + } + } + + public void removeOnUpdateBlocklistListener() { + synchronized (this) { + this.updateBlocklistListenerCount--; + if (this.updateBlocklistListenerCount <= 0) { + this.updateBlocklistListenerCount = 0; + this.mOnUpdateBlocklist = null; + if (checkListeners()) { + switchToBackground(); + } + } + } + } + + public void setOnKeyStatusUpdatedListener(final OnKeyStatusUpdated listener) { + synchronized (this) { + if (checkListeners()) { + switchToForeground(); + } + this.mOnKeyStatusUpdated = listener; + if (this.keyStatusUpdatedListenerCount < 2) { + this.keyStatusUpdatedListenerCount++; + } + } + } + + public void removeOnNewKeysAvailableListener() { + synchronized (this) { + this.keyStatusUpdatedListenerCount--; + if (this.keyStatusUpdatedListenerCount <= 0) { + this.keyStatusUpdatedListenerCount = 0; + this.mOnKeyStatusUpdated = null; + if (checkListeners()) { + switchToBackground(); + } + } + } + } + + public void setOnMucRosterUpdateListener(OnMucRosterUpdate listener) { + synchronized (this) { + if (checkListeners()) { + switchToForeground(); + } + this.mOnMucRosterUpdate = listener; + if (this.mucRosterChangedListenerCount < 2) { + this.mucRosterChangedListenerCount++; + } + } + } + + public void removeOnMucRosterUpdateListener() { + synchronized (this) { + this.mucRosterChangedListenerCount--; + if (this.mucRosterChangedListenerCount <= 0) { + this.mucRosterChangedListenerCount = 0; + this.mOnMucRosterUpdate = null; + if (checkListeners()) { + switchToBackground(); + } + } + } + } + + public boolean checkListeners() { + return (this.mOnAccountUpdate == null + && this.mOnConversationUpdate == null + && this.mOnRosterUpdate == null + && this.mOnCaptchaRequested == null + && this.mOnUpdateBlocklist == null + && this.mOnShowErrorToast == null + && this.mOnKeyStatusUpdated == null); + } + + private void switchToForeground() { + final boolean broadcastLastActivity = broadcastLastActivity(); + for (Conversation conversation : getConversations()) { + conversation.setIncomingChatState(ChatState.ACTIVE); + } + for (Account account : getAccounts()) { + if (account.getStatus() == Account.State.ONLINE) { + account.deactivateGracePeriod(); + final XmppConnection connection = account.getXmppConnection(); + if (connection != null) { + if (connection.getFeatures().csi()) { + connection.sendActive(); + } + if (broadcastLastActivity) { + sendPresence(account, false); //send new presence but don't include idle because we are not + } + } + } + } + Log.d(Config.LOGTAG, "app switched into foreground"); + } + + private void switchToBackground() { + final boolean broadcastLastActivity = broadcastLastActivity(); + for (Account account : getAccounts()) { + if (account.getStatus() == Account.State.ONLINE) { + XmppConnection connection = account.getXmppConnection(); + if (connection != null) { + if (broadcastLastActivity) { + sendPresence(account, broadcastLastActivity); + } + if (connection.getFeatures().csi()) { + connection.sendInactive(); + } + } + } + } + this.mNotificationService.setIsInForeground(false); + Log.d(Config.LOGTAG, "app switched into background"); + } + + private void connectMultiModeConversations(Account account) { + List conversations = getConversations(); + for (Conversation conversation : conversations) { + if (conversation.getMode() == Conversation.MODE_MULTI && conversation.getAccount() == account) { + joinMuc(conversation); + } + } + } + + public void joinMuc(Conversation conversation) { + joinMuc(conversation, null); + } + + private void joinMuc(Conversation conversation, final OnConferenceJoined onConferenceJoined) { + Account account = conversation.getAccount(); + account.pendingConferenceJoins.remove(conversation); + account.pendingConferenceLeaves.remove(conversation); + if (account.getStatus() == Account.State.ONLINE) { + conversation.resetMucOptions(); + if (onConferenceJoined != null) { + conversation.getMucOptions().flagNoAutoPushConfiguration(); + } + conversation.setHasMessagesLeftOnServer(false); + fetchConferenceConfiguration(conversation, new OnConferenceConfigurationFetched() { + + private void join(Conversation conversation) { + Account account = conversation.getAccount(); + final MucOptions mucOptions = conversation.getMucOptions(); + final Jid joinJid = mucOptions.getSelf().getFullJid(); + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": joining conversation " + joinJid.toString()); + PresencePacket packet = mPresenceGenerator.selfPresence(account, Presence.Status.ONLINE, mucOptions.nonanonymous()); + packet.setTo(joinJid); + Element x = packet.addChild("x", "http://jabber.org/protocol/muc"); + if (conversation.getMucOptions().getPassword() != null) { + x.addChild("password").setContent(mucOptions.getPassword()); + } + + if (mucOptions.mamSupport()) { + // Use MAM instead of the limited muc history to get history + x.addChild("history").setAttribute("maxchars", "0"); + } else { + // Fallback to muc history + x.addChild("history").setAttribute("since", PresenceGenerator.getTimestamp(conversation.getLastMessageTransmitted())); + } + sendPresencePacket(account, packet); + if (onConferenceJoined != null) { + onConferenceJoined.onConferenceJoined(conversation); + } + if (!joinJid.equals(conversation.getJid())) { + conversation.setContactJid(joinJid); + databaseBackend.updateConversation(conversation); + } + + if (mucOptions.mamSupport()) { + getMessageArchiveService().catchupMUC(conversation); + } + if (mucOptions.membersOnly() && mucOptions.nonanonymous()) { + fetchConferenceMembers(conversation); + } + sendUnsentMessages(conversation); + } + + @Override + public void onConferenceConfigurationFetched(Conversation conversation) { + join(conversation); + } + + @Override + public void onFetchFailed(final Conversation conversation, Element error) { + if (error != null && "remote-server-not-found".equals(error.getName())) { + conversation.getMucOptions().setError(MucOptions.Error.SERVER_NOT_FOUND); + } else { + join(conversation); + fetchConferenceConfiguration(conversation); + } + } + }); + updateConversationUi(); + } else { + account.pendingConferenceJoins.add(conversation); + conversation.resetMucOptions(); + conversation.setHasMessagesLeftOnServer(false); + updateConversationUi(); + } + } + + private void fetchConferenceMembers(final Conversation conversation) { + final Account account = conversation.getAccount(); + final String[] affiliations = {"member", "admin", "owner"}; + OnIqPacketReceived callback = new OnIqPacketReceived() { + + private int i = 0; + + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + + Element query = packet.query("http://jabber.org/protocol/muc#admin"); + if (packet.getType() == IqPacket.TYPE.RESULT && query != null) { + for (Element child : query.getChildren()) { + if ("item".equals(child.getName())) { + MucOptions.User user = AbstractParser.parseItem(conversation, child); + if (!user.realJidMatchesAccount()) { + conversation.getMucOptions().updateUser(user); + } + } + } + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not request affiliation " + affiliations[i] + " in " + conversation.getJid().toBareJid()); + } + ++i; + if (i >= affiliations.length) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": retrieved members for " + conversation.getJid().toBareJid() + ": " + conversation.getMucOptions().getMembers()); + getAvatarService().clear(conversation); + updateMucRosterUi(); + updateConversationUi(); + } + } + }; + for (String affiliation : affiliations) { + sendIqPacket(account, mIqGenerator.queryAffiliation(conversation, affiliation), callback); + } + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": fetching members for " + conversation.getName()); + } + + public void providePasswordForMuc(Conversation conversation, String password) { + if (conversation.getMode() == Conversation.MODE_MULTI) { + conversation.getMucOptions().setPassword(password); + if (conversation.getBookmark() != null) { + if (respectAutojoin()) { + conversation.getBookmark().setAutojoin(true); + } + pushBookmarks(conversation.getAccount()); + } + updateConversation(conversation); + joinMuc(conversation); + } + } + + public void renameInMuc(final Conversation conversation, final String nick, final UiCallback callback) { + final MucOptions options = conversation.getMucOptions(); + final Jid joinJid = options.createJoinJid(nick); + if (options.online()) { + Account account = conversation.getAccount(); + options.setOnRenameListener(new OnRenameListener() { + + @Override + public void onSuccess() { + conversation.setContactJid(joinJid); + databaseBackend.updateConversation(conversation); + Bookmark bookmark = conversation.getBookmark(); + if (bookmark != null) { + bookmark.setNick(nick); + pushBookmarks(bookmark.getAccount()); + } + callback.success(conversation); + } + + @Override + public void onFailure() { + callback.error(R.string.nick_in_use, conversation); + } + }); + + PresencePacket packet = new PresencePacket(); + packet.setTo(joinJid); + packet.setFrom(conversation.getAccount().getJid()); + + String sig = account.getPgpSignature(); + if (sig != null) { + packet.addChild("status").setContent("online"); + packet.addChild("x", "jabber:x:signed").setContent(sig); + } + sendPresencePacket(account, packet); + } else { + conversation.setContactJid(joinJid); + databaseBackend.updateConversation(conversation); + if (conversation.getAccount().getStatus() == Account.State.ONLINE) { + Bookmark bookmark = conversation.getBookmark(); + if (bookmark != null) { + bookmark.setNick(nick); + pushBookmarks(bookmark.getAccount()); + } + joinMuc(conversation); + } + } + } + + public void leaveMuc(Conversation conversation) { + leaveMuc(conversation, false); + } + + private void leaveMuc(Conversation conversation, boolean now) { + Account account = conversation.getAccount(); + account.pendingConferenceJoins.remove(conversation); + account.pendingConferenceLeaves.remove(conversation); + if (account.getStatus() == Account.State.ONLINE || now) { + PresencePacket packet = new PresencePacket(); + packet.setTo(conversation.getMucOptions().getSelf().getFullJid()); + packet.setFrom(conversation.getAccount().getJid()); + packet.setAttribute("type", "unavailable"); + sendPresencePacket(conversation.getAccount(), packet); + conversation.getMucOptions().setOffline(); + conversation.deregisterWithBookmark(); + Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + + ": leaving muc " + conversation.getJid()); + } else { + account.pendingConferenceLeaves.add(conversation); + } + } + + private String findConferenceServer(final Account account) { + String server; + if (account.getXmppConnection() != null) { + server = account.getXmppConnection().getMucServer(); + if (server != null) { + return server; + } + } + for (Account other : getAccounts()) { + if (other != account && other.getXmppConnection() != null) { + server = other.getXmppConnection().getMucServer(); + if (server != null) { + return server; + } + } + } + return null; + } + + public void createAdhocConference(final Account account, + final String subject, + final Iterable jids, + final UiCallback callback) { + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": creating adhoc conference with " + jids.toString()); + if (account.getStatus() == Account.State.ONLINE) { + try { + String server = findConferenceServer(account); + if (server == null) { + if (callback != null) { + callback.error(R.string.no_conference_server_found, null); + } + return; + } + final Jid jid = Jid.fromParts(new BigInteger(64, getRNG()).toString(Character.MAX_RADIX), server, null); + final Conversation conversation = findOrCreateConversation(account, jid, true); + joinMuc(conversation, new OnConferenceJoined() { + @Override + public void onConferenceJoined(final Conversation conversation) { + pushConferenceConfiguration(conversation, IqGenerator.defaultRoomConfiguration(), new OnConferenceOptionsPushed() { + @Override + public void onPushSucceeded() { + if (subject != null && !subject.trim().isEmpty()) { + pushSubjectToConference(conversation, subject.trim()); + } + for (Jid invite : jids) { + invite(conversation, invite); + } + if (account.countPresences() > 1) { + directInvite(conversation, account.getJid().toBareJid()); + } + saveConversationAsBookmark(conversation, subject); + if (callback != null) { + callback.success(conversation); + } + } + + @Override + public void onPushFailed() { + archiveConversation(conversation); + if (callback != null) { + callback.error(R.string.conference_creation_failed, conversation); + } + } + }); + } + }); + } catch (InvalidJidException e) { + if (callback != null) { + callback.error(R.string.conference_creation_failed, null); + } + } + } else { + if (callback != null) { + callback.error(R.string.not_connected_try_again, null); + } + } + } + + public void fetchConferenceConfiguration(final Conversation conversation) { + fetchConferenceConfiguration(conversation, null); + } + + public void fetchConferenceConfiguration(final Conversation conversation, final OnConferenceConfigurationFetched callback) { + IqPacket request = new IqPacket(IqPacket.TYPE.GET); + request.setTo(conversation.getJid().toBareJid()); + request.query("http://jabber.org/protocol/disco#info"); + sendIqPacket(conversation.getAccount(), request, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + Element query = packet.findChild("query", "http://jabber.org/protocol/disco#info"); + if (packet.getType() == IqPacket.TYPE.RESULT && query != null) { + ArrayList features = new ArrayList<>(); + for (Element child : query.getChildren()) { + if (child != null && child.getName().equals("feature")) { + String var = child.getAttribute("var"); + if (var != null) { + features.add(var); + } + } + } + Element form = query.findChild("x", "jabber:x:data"); + if (form != null) { + conversation.getMucOptions().updateFormData(Data.parse(form)); + } + conversation.getMucOptions().updateFeatures(features); + if (callback != null) { + callback.onConferenceConfigurationFetched(conversation); + } + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": fetched muc configuration for " + conversation.getJid().toBareJid() + " - " + features.toString()); + updateConversationUi(); + } else if (packet.getType() == IqPacket.TYPE.ERROR) { + if (callback != null) { + callback.onFetchFailed(conversation, packet.getError()); + } + } + } + }); + } + + public void pushConferenceConfiguration(final Conversation conversation, final Bundle options, final OnConferenceOptionsPushed callback) { + IqPacket request = new IqPacket(IqPacket.TYPE.GET); + request.setTo(conversation.getJid().toBareJid()); + request.query("http://jabber.org/protocol/muc#owner"); + sendIqPacket(conversation.getAccount(), request, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.RESULT) { + Data data = Data.parse(packet.query().findChild("x", "jabber:x:data")); + for (Field field : data.getFields()) { + if (options.containsKey(field.getFieldName())) { + field.setValue(options.getString(field.getFieldName())); + } + } + data.submit(); + IqPacket set = new IqPacket(IqPacket.TYPE.SET); + set.setTo(conversation.getJid().toBareJid()); + set.query("http://jabber.org/protocol/muc#owner").addChild(data); + sendIqPacket(account, set, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (callback != null) { + if (packet.getType() == IqPacket.TYPE.RESULT) { + callback.onPushSucceeded(); + } else { + callback.onPushFailed(); + } + } + } + }); + } else { + if (callback != null) { + callback.onPushFailed(); + } + } + } + }); + } + + public void pushSubjectToConference(final Conversation conference, final String subject) { + MessagePacket packet = this.getMessageGenerator().conferenceSubject(conference, subject); + this.sendMessagePacket(conference.getAccount(), packet); + final MucOptions mucOptions = conference.getMucOptions(); + final MucOptions.User self = mucOptions.getSelf(); + if (!mucOptions.persistent() && self.getAffiliation().ranks(MucOptions.Affiliation.OWNER)) { + Bundle options = new Bundle(); + options.putString("muc#roomconfig_persistentroom", "1"); + this.pushConferenceConfiguration(conference, options, null); + } + } + + public void changeAffiliationInConference(final Conversation conference, Jid user, final MucOptions.Affiliation affiliation, final OnAffiliationChanged callback) { + final Jid jid = user.toBareJid(); + IqPacket request = this.mIqGenerator.changeAffiliation(conference, jid, affiliation.toString()); + sendIqPacket(conference.getAccount(), request, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.RESULT) { + conference.getMucOptions().changeAffiliation(jid, affiliation); + getAvatarService().clear(conference); + callback.onAffiliationChangedSuccessful(jid); + } else { + callback.onAffiliationChangeFailed(jid, R.string.could_not_change_affiliation); + } + } + }); + } + + public void changeAffiliationsInConference(final Conversation conference, MucOptions.Affiliation before, MucOptions.Affiliation after) { + List jids = new ArrayList<>(); + for (MucOptions.User user : conference.getMucOptions().getUsers()) { + if (user.getAffiliation() == before && user.getRealJid() != null) { + jids.add(user.getRealJid()); + } + } + IqPacket request = this.mIqGenerator.changeAffiliation(conference, jids, after.toString()); + sendIqPacket(conference.getAccount(), request, mDefaultIqHandler); + } + + public void changeRoleInConference(final Conversation conference, final String nick, MucOptions.Role role, final OnRoleChanged callback) { + IqPacket request = this.mIqGenerator.changeRole(conference, nick, role.toString()); + Log.d(Config.LOGTAG, request.toString()); + sendIqPacket(conference.getAccount(), request, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + Log.d(Config.LOGTAG, packet.toString()); + if (packet.getType() == IqPacket.TYPE.RESULT) { + callback.onRoleChangedSuccessful(nick); + } else { + callback.onRoleChangeFailed(nick, R.string.could_not_change_role); + } + } + }); + } + + private void disconnect(Account account, boolean force) { + if ((account.getStatus() == Account.State.ONLINE) + || (account.getStatus() == Account.State.DISABLED)) { + final XmppConnection connection = account.getXmppConnection(); + if (!force) { + List conversations = getConversations(); + for (Conversation conversation : conversations) { + if (conversation.getAccount() == account) { + if (conversation.getMode() == Conversation.MODE_MULTI) { + leaveMuc(conversation, true); + } else { + if (conversation.endOtrIfNeeded()) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + + ": ended otr session with " + + conversation.getJid()); + } + } + } + } + sendOfflinePresence(account); + } + connection.disconnect(force); + } + } + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + public void updateMessage(Message message) { + databaseBackend.updateMessage(message); + updateConversationUi(); + } + + public void updateMessage(Message message, String uuid) { + databaseBackend.updateMessage(message, uuid); + updateConversationUi(); + } + + protected void syncDirtyContacts(Account account) { + for (Contact contact : account.getRoster().getContacts()) { + if (contact.getOption(Contact.Options.DIRTY_PUSH)) { + pushContactToServer(contact); + } + if (contact.getOption(Contact.Options.DIRTY_DELETE)) { + deleteContactOnServer(contact); + } + } + } + + public void createContact(Contact contact) { + boolean autoGrant = getPreferences().getBoolean("grant_new_contacts", true); + if (autoGrant) { + contact.setOption(Contact.Options.PREEMPTIVE_GRANT); + contact.setOption(Contact.Options.ASKING); + } + pushContactToServer(contact); + } + + public void onOtrSessionEstablished(Conversation conversation) { + final Account account = conversation.getAccount(); + final Session otrSession = conversation.getOtrSession(); + Log.d(Config.LOGTAG, + account.getJid().toBareJid() + " otr session established with " + + conversation.getJid() + "/" + + otrSession.getSessionID().getUserID()); + conversation.findUnsentMessagesWithEncryption(Message.ENCRYPTION_OTR, new Conversation.OnMessageFound() { + + @Override + public void onMessageFound(Message message) { + SessionID id = otrSession.getSessionID(); + try { + message.setCounterpart(Jid.fromString(id.getAccountID() + "/" + id.getUserID())); + } catch (InvalidJidException e) { + return; + } + if (message.needsUploading()) { + mJingleConnectionManager.createNewConnection(message); + } else { + MessagePacket outPacket = mMessageGenerator.generateOtrChat(message); + if (outPacket != null) { + mMessageGenerator.addDelay(outPacket, message.getTimeSent()); + message.setStatus(Message.STATUS_SEND); + databaseBackend.updateMessage(message); + sendMessagePacket(account, outPacket); + } + } + updateConversationUi(); + } + }); + } + + public boolean renewSymmetricKey(Conversation conversation) { + Account account = conversation.getAccount(); + byte[] symmetricKey = new byte[32]; + this.mRandom.nextBytes(symmetricKey); + Session otrSession = conversation.getOtrSession(); + if (otrSession != null) { + MessagePacket packet = new MessagePacket(); + packet.setType(MessagePacket.TYPE_CHAT); + packet.setFrom(account.getJid()); + MessageGenerator.addMessageHints(packet); + packet.setAttribute("to", otrSession.getSessionID().getAccountID() + "/" + + otrSession.getSessionID().getUserID()); + try { + packet.setBody(otrSession + .transformSending(CryptoHelper.FILETRANSFER + + CryptoHelper.bytesToHex(symmetricKey))[0]); + sendMessagePacket(account, packet); + conversation.setSymmetricKey(symmetricKey); + return true; + } catch (OtrException e) { + return false; + } + } + return false; + } + + public void pushContactToServer(final Contact contact) { + contact.resetOption(Contact.Options.DIRTY_DELETE); + contact.setOption(Contact.Options.DIRTY_PUSH); + final Account account = contact.getAccount(); + if (account.getStatus() == Account.State.ONLINE) { + final boolean ask = contact.getOption(Contact.Options.ASKING); + final boolean sendUpdates = contact + .getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST) + && contact.getOption(Contact.Options.PREEMPTIVE_GRANT); + final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); + iq.query(Xmlns.ROSTER).addChild(contact.asElement()); + account.getXmppConnection().sendIqPacket(iq, mDefaultIqHandler); + if (sendUpdates) { + sendPresencePacket(account, + mPresenceGenerator.sendPresenceUpdatesTo(contact)); + } + if (ask) { + sendPresencePacket(account, + mPresenceGenerator.requestPresenceUpdatesFrom(contact)); + } + } + } + + public void publishAvatar(Account account, Uri image, UiCallback callback) { + final Bitmap.CompressFormat format = Config.AVATAR_FORMAT; + final int size = Config.AVATAR_SIZE; + final Avatar avatar = getFileBackend().getPepAvatar(image, size, format); + if (avatar != null) { + avatar.height = size; + avatar.width = size; + if (format.equals(Bitmap.CompressFormat.WEBP)) { + avatar.type = "image/webp"; + } else if (format.equals(Bitmap.CompressFormat.JPEG)) { + avatar.type = "image/jpeg"; + } else if (format.equals(Bitmap.CompressFormat.PNG)) { + avatar.type = "image/png"; + } + if (!getFileBackend().save(avatar)) { + callback.error(R.string.error_saving_avatar, avatar); + return; + } + publishAvatar(account, avatar, callback); + } else { + callback.error(R.string.error_publish_avatar_converting, null); + } + } + + public void publishAvatar(Account account, final Avatar avatar, final UiCallback callback) { + final IqPacket packet = this.mIqGenerator.publishAvatar(avatar); + this.sendIqPacket(account, packet, new OnIqPacketReceived() { + + @Override + public void onIqPacketReceived(Account account, IqPacket result) { + if (result.getType() == IqPacket.TYPE.RESULT) { + final IqPacket packet = XmppConnectionService.this.mIqGenerator + .publishAvatarMetadata(avatar); + sendIqPacket(account, packet, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket result) { + if (result.getType() == IqPacket.TYPE.RESULT) { + if (account.setAvatar(avatar.getFilename())) { + getAvatarService().clear(account); + databaseBackend.updateAccount(account); + } + if (callback != null) { + callback.success(avatar); + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": published avatar"); + } + } else { + if (callback != null) { + callback.error( + R.string.error_publish_avatar_server_reject, + avatar); + } + } + } + }); + } else { + if (callback != null) { + callback.error( + R.string.error_publish_avatar_server_reject, + avatar); + } + } + } + }); + } + + public void republishAvatarIfNeeded(Account account) { + if (account.getAxolotlService().isPepBroken()) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": skipping republication of avatar because pep is broken"); + return; + } + IqPacket packet = this.mIqGenerator.retrieveAvatarMetaData(null); + this.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 = fileBackend.getStoredPepAvatar(account.getAvatar()); + if (avatar != null) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": avatar on server was null. republishing"); + publishAvatar(account, fileBackend.getStoredPepAvatar(account.getAvatar()), null); + } else { + Log.e(Config.LOGTAG, account.getJid().toBareJid() + ": error rereading avatar"); + } + } + } + } + }); + } + + public void fetchAvatar(Account account, Avatar avatar) { + fetchAvatar(account, avatar, null); + } + + public void fetchAvatar(Account account, final Avatar avatar, final UiCallback callback) { + final String KEY = generateFetchKey(account, avatar); + synchronized (this.mInProgressAvatarFetches) { + if (!this.mInProgressAvatarFetches.contains(KEY)) { + switch (avatar.origin) { + case PEP: + this.mInProgressAvatarFetches.add(KEY); + fetchAvatarPep(account, avatar, callback); + break; + case VCARD: + this.mInProgressAvatarFetches.add(KEY); + fetchAvatarVcard(account, avatar, callback); + break; + } + } + } + } + + private void fetchAvatarPep(Account account, final Avatar avatar, final UiCallback callback) { + IqPacket packet = this.mIqGenerator.retrievePepAvatar(avatar); + sendIqPacket(account, packet, new OnIqPacketReceived() { + + @Override + public void onIqPacketReceived(Account account, IqPacket result) { + synchronized (mInProgressAvatarFetches) { + mInProgressAvatarFetches.remove(generateFetchKey(account, avatar)); + } + final String ERROR = account.getJid().toBareJid() + + ": fetching avatar for " + avatar.owner + " failed "; + if (result.getType() == IqPacket.TYPE.RESULT) { + avatar.image = mIqParser.avatarData(result); + if (avatar.image != null) { + if (getFileBackend().save(avatar)) { + if (account.getJid().toBareJid().equals(avatar.owner)) { + if (account.setAvatar(avatar.getFilename())) { + databaseBackend.updateAccount(account); + } + getAvatarService().clear(account); + updateConversationUi(); + updateAccountUi(); + } else { + Contact contact = account.getRoster() + .getContact(avatar.owner); + contact.setAvatar(avatar); + getAvatarService().clear(contact); + updateConversationUi(); + updateRosterUi(); + } + if (callback != null) { + callback.success(avatar); + } + Log.d(Config.LOGTAG, account.getJid().toBareJid() + + ": successfully fetched pep avatar for " + avatar.owner); + return; + } + } else { + + Log.d(Config.LOGTAG, ERROR + "(parsing error)"); + } + } else { + Element error = result.findChild("error"); + if (error == null) { + Log.d(Config.LOGTAG, ERROR + "(server error)"); + } else { + Log.d(Config.LOGTAG, ERROR + error.toString()); + } + } + if (callback != null) { + callback.error(0, null); + } + + } + }); + } + + private void fetchAvatarVcard(final Account account, final Avatar avatar, final UiCallback callback) { + IqPacket packet = this.mIqGenerator.retrieveVcardAvatar(avatar); + this.sendIqPacket(account, packet, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + synchronized (mInProgressAvatarFetches) { + mInProgressAvatarFetches.remove(generateFetchKey(account, avatar)); + } + if (packet.getType() == IqPacket.TYPE.RESULT) { + Element vCard = packet.findChild("vCard", "vcard-temp"); + Element photo = vCard != null ? vCard.findChild("PHOTO") : null; + String image = photo != null ? photo.findChildContent("BINVAL") : null; + if (image != null) { + avatar.image = image; + if (getFileBackend().save(avatar)) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + + ": successfully fetched vCard avatar for " + avatar.owner); + if (avatar.owner.isBareJid()) { + if (account.getJid().toBareJid().equals(avatar.owner) && account.getAvatar() == null) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": had no avatar. replacing with vcard"); + account.setAvatar(avatar.getFilename()); + databaseBackend.updateAccount(account); + getAvatarService().clear(account); + updateAccountUi(); + } else { + Contact contact = account.getRoster().getContact(avatar.owner); + contact.setAvatar(avatar); + getAvatarService().clear(contact); + updateRosterUi(); + } + updateConversationUi(); + } else { + Conversation conversation = find(account, avatar.owner.toBareJid()); + if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) { + MucOptions.User user = conversation.getMucOptions().findUserByFullJid(avatar.owner); + if (user != null) { + if (user.setAvatar(avatar)) { + getAvatarService().clear(user); + updateConversationUi(); + updateMucRosterUi(); + } + } + } + } + } + } + } + } + }); + } + + public void checkForAvatar(Account account, final UiCallback callback) { + IqPacket packet = this.mIqGenerator.retrieveAvatarMetaData(null); + this.sendIqPacket(account, packet, new OnIqPacketReceived() { + + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.RESULT) { + Element pubsub = packet.findChild("pubsub", "http://jabber.org/protocol/pubsub"); + if (pubsub != null) { + Element items = pubsub.findChild("items"); + if (items != null) { + Avatar avatar = Avatar.parseMetadata(items); + if (avatar != null) { + avatar.owner = account.getJid().toBareJid(); + if (fileBackend.isAvatarCached(avatar)) { + if (account.setAvatar(avatar.getFilename())) { + databaseBackend.updateAccount(account); + } + getAvatarService().clear(account); + callback.success(avatar); + } else { + fetchAvatarPep(account, avatar, callback); + } + return; + } + } + } + } + callback.error(0, null); + } + }); + } + + public void deleteContactOnServer(Contact contact) { + contact.resetOption(Contact.Options.PREEMPTIVE_GRANT); + contact.resetOption(Contact.Options.DIRTY_PUSH); + contact.setOption(Contact.Options.DIRTY_DELETE); + Account account = contact.getAccount(); + if (account.getStatus() == Account.State.ONLINE) { + IqPacket iq = new IqPacket(IqPacket.TYPE.SET); + Element item = iq.query(Xmlns.ROSTER).addChild("item"); + item.setAttribute("jid", contact.getJid().toString()); + item.setAttribute("subscription", "remove"); + account.getXmppConnection().sendIqPacket(iq, mDefaultIqHandler); + } + } + + public void updateConversation(final Conversation conversation) { + mDatabaseExecutor.execute(new Runnable() { + @Override + public void run() { + databaseBackend.updateConversation(conversation); + } + }); + } + + private void reconnectAccount(final Account account, final boolean force, final boolean interactive) { + synchronized (account) { + XmppConnection connection = account.getXmppConnection(); + if (connection == null) { + connection = createConnection(account); + account.setXmppConnection(connection); + } else { + connection.interrupt(); + } + if (!account.isOptionSet(Account.OPTION_DISABLED)) { + if (!force) { + disconnect(account, false); + } + Thread thread = new Thread(connection); + connection.setInteractive(interactive); + connection.prepareNewConnection(); + thread.start(); + scheduleWakeUpCall(Config.CONNECT_DISCO_TIMEOUT, account.getUuid().hashCode()); + } else { + disconnect(account, force); + account.getRoster().clearPresences(); + connection.resetEverything(); + account.getAxolotlService().resetBrokenness(); + } + } + } + + public void reconnectAccountInBackground(final Account account) { + new Thread(new Runnable() { + @Override + public void run() { + reconnectAccount(account, false, true); + } + }).start(); + } + + public void invite(Conversation conversation, Jid contact) { + Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + ": inviting " + contact + " to " + conversation.getJid().toBareJid()); + MessagePacket packet = mMessageGenerator.invite(conversation, contact); + sendMessagePacket(conversation.getAccount(), packet); + } + + public void directInvite(Conversation conversation, Jid jid) { + MessagePacket packet = mMessageGenerator.directInvite(conversation, jid); + sendMessagePacket(conversation.getAccount(), packet); + } + + public void resetSendingToWaiting(Account account) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": reset 'sending' messages to 'waiting'"); + for (Conversation conversation : getConversations()) { + if (conversation.getAccount() == account) { + conversation.findUnsentTextMessages(new Conversation.OnMessageFound() { + + @Override + public void onMessageFound(Message message) { + markMessage(message, Message.STATUS_WAITING); + } + }); + } + } + } + + public Message markMessage(final Account account, final Jid recipient, final String uuid, final int status) { + return markMessage(account, recipient, uuid, status, null); + } + + public Message markMessage(final Account account, final Jid recipient, final String uuid, final int status, String errorMessage) { + if (uuid == null) { + return null; + } + for (Conversation conversation : getConversations()) { + if (conversation.getJid().toBareJid().equals(recipient) && conversation.getAccount() == account) { + final Message message = conversation.findSentMessageWithUuidOrRemoteId(uuid); + if (message != null) { + markMessage(message, status, errorMessage); + } + return message; + } + } + return null; + } + + public boolean markMessage(Conversation conversation, String uuid, int status) { + if (uuid == null) { + return false; + } else { + Message message = conversation.findSentMessageWithUuid(uuid); + if (message != null) { + markMessage(message, status); + return true; + } else { + return false; + } + } + } + + public void markMessage(Message message, int status) { + markMessage(message, status, null); + } + + public void markMessage(Message message, int status, String errorMessage) { + if (status == Message.STATUS_SEND_FAILED + && (message.getStatus() == Message.STATUS_SEND_RECEIVED || message + .getStatus() == Message.STATUS_SEND_DISPLAYED)) { + return; + } + message.setErrorMessage(errorMessage); + message.setStatus(status); + databaseBackend.updateMessage(message); + updateConversationUi(); + } + + public SharedPreferences getPreferences() { + return PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()); + } + + public boolean confirmMessages() { + return getPreferences().getBoolean("confirm_messages", true); + } + + public boolean allowMessageCorrection() { + return getPreferences().getBoolean("allow_message_correction", true); + } + + public boolean sendChatStates() { + return getPreferences().getBoolean("chat_states", true); + } + + public boolean saveEncryptedMessages() { + return !getPreferences().getBoolean("dont_save_encrypted", false); + } + + private boolean respectAutojoin() { + return getPreferences().getBoolean("autojoin", true); + } + + public boolean indicateReceived() { + return getPreferences().getBoolean("indicate_received", true); + } + + public boolean useTorToConnect() { + return Config.FORCE_ORBOT || getPreferences().getBoolean("use_tor", false); + } + + public boolean showExtendedConnectionOptions() { + return getPreferences().getBoolean("show_connection_options", false); + } + + public boolean broadcastLastActivity() { + return getPreferences().getBoolean("last_activity", true); + } + + public int unreadCount() { + int count = 0; + for (Conversation conversation : getConversations()) { + count += conversation.unreadCount(); + } + return count; + } + + public void vibrate() { + Log.d(Config.LOGTAG, "Notification: short vibrate"); + Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE); + vibrator.vibrate(100); + } + + + public void showErrorToastInUi(int resId) { + if (mOnShowErrorToast != null) { + mOnShowErrorToast.onShowErrorToast(resId); + } + } + + public void updateConversationUi() { + if (mOnConversationUpdate != null) { + mOnConversationUpdate.onConversationUpdate(); + } + } + + public void updateAccountUi() { + if (mOnAccountUpdate != null) { + mOnAccountUpdate.onAccountUpdate(); + } + } + + public void updateRosterUi() { + if (mOnRosterUpdate != null) { + mOnRosterUpdate.onRosterUpdate(); + } + } + + public boolean displayCaptchaRequest(Account account, String id, Data data, Bitmap captcha) { + if (mOnCaptchaRequested != null) { + DisplayMetrics metrics = getApplicationContext().getResources().getDisplayMetrics(); + Bitmap scaled = Bitmap.createScaledBitmap(captcha, (int) (captcha.getWidth() * metrics.scaledDensity), + (int) (captcha.getHeight() * metrics.scaledDensity), false); + + mOnCaptchaRequested.onCaptchaRequested(account, id, data, scaled); + return true; + } + return false; + } + + public void updateBlocklistUi(final OnUpdateBlocklist.Status status) { + if (mOnUpdateBlocklist != null) { + mOnUpdateBlocklist.OnUpdateBlocklist(status); + } + } + + public void updateMucRosterUi() { + if (mOnMucRosterUpdate != null) { + mOnMucRosterUpdate.onMucRosterUpdate(); + } + } + + public void keyStatusUpdated(AxolotlService.FetchStatus report) { + if (mOnKeyStatusUpdated != null) { + mOnKeyStatusUpdated.onKeyStatusUpdated(report); + } + } + + public Account findAccountByJid(final Jid accountJid) { + for (Account account : this.accounts) { + if (account.getJid().toBareJid().equals(accountJid.toBareJid())) { + return account; + } + } + return null; + } + + public Conversation findConversationByUuid(String uuid) { + for (Conversation conversation : getConversations()) { + if (conversation.getUuid().equals(uuid)) { + return conversation; + } + } + return null; + } + + public boolean markRead(final Conversation conversation) { + return markRead(conversation, true); + } + + public boolean markRead(final Conversation conversation, boolean clear) { + if (clear) { + mNotificationService.clear(conversation); + } + final List readMessages = conversation.markRead(); + if (readMessages.size() > 0) { + Runnable runnable = new Runnable() { + @Override + public void run() { + for (Message message : readMessages) { + databaseBackend.updateMessage(message); + } + } + }; + mDatabaseExecutor.execute(runnable); + updateUnreadCountBadge(); + return true; + } else { + return false; + } + } + + public synchronized void updateUnreadCountBadge() { + int count = unreadCount(); + if (unreadCount != count) { + Log.d(Config.LOGTAG, "update unread count to " + count); + if (count > 0) { + ShortcutBadger.applyCount(getApplicationContext(), count); + } else { + ShortcutBadger.removeCount(getApplicationContext()); + } + unreadCount = count; + } + } + + public void sendReadMarker(final Conversation conversation) { + final Message markable = conversation.getLatestMarkableMessage(); + if (this.markRead(conversation)) { + updateConversationUi(); + } + if (confirmMessages() && markable != null && markable.trusted() && markable.getRemoteMsgId() != null) { + Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + ": sending read marker to " + markable.getCounterpart().toString()); + Account account = conversation.getAccount(); + final Jid to = markable.getCounterpart(); + MessagePacket packet = mMessageGenerator.confirm(account, to, markable.getRemoteMsgId()); + this.sendMessagePacket(conversation.getAccount(), packet); + } + } + + public SecureRandom getRNG() { + return this.mRandom; + } + + public MemorizingTrustManager getMemorizingTrustManager() { + return this.mMemorizingTrustManager; + } + + public void setMemorizingTrustManager(MemorizingTrustManager trustManager) { + this.mMemorizingTrustManager = trustManager; + } + + public void updateMemorizingTrustmanager() { + final MemorizingTrustManager tm; + final boolean dontTrustSystemCAs = getPreferences().getBoolean("dont_trust_system_cas", false); + if (dontTrustSystemCAs) { + tm = new MemorizingTrustManager(getApplicationContext(), null); + } else { + tm = new MemorizingTrustManager(getApplicationContext()); + } + setMemorizingTrustManager(tm); + } + + public PowerManager getPowerManager() { + return this.pm; + } + + public LruCache getBitmapCache() { + return this.mBitmapCache; + } + + public void syncRosterToDisk(final Account account) { + Runnable runnable = new Runnable() { + + @Override + public void run() { + databaseBackend.writeRoster(account.getRoster()); + } + }; + mDatabaseExecutor.execute(runnable); + + } + + public List getKnownHosts() { + final List hosts = new ArrayList<>(); + for (final Account account : getAccounts()) { + if (!hosts.contains(account.getServer().toString())) { + hosts.add(account.getServer().toString()); + } + for (final Contact contact : account.getRoster().getContacts()) { + if (contact.showInRoster()) { + final String server = contact.getServer().toString(); + if (server != null && !hosts.contains(server)) { + hosts.add(server); + } + } + } + } + if (Config.DOMAIN_LOCK != null && !hosts.contains(Config.DOMAIN_LOCK)) { + hosts.add(Config.DOMAIN_LOCK); + } + if (Config.MAGIC_CREATE_DOMAIN != null && !hosts.contains(Config.MAGIC_CREATE_DOMAIN)) { + hosts.add(Config.MAGIC_CREATE_DOMAIN); + } + return hosts; + } + + public List getKnownConferenceHosts() { + final ArrayList mucServers = new ArrayList<>(); + for (final Account account : accounts) { + if (account.getXmppConnection() != null) { + final String server = account.getXmppConnection().getMucServer(); + if (server != null && !mucServers.contains(server)) { + mucServers.add(server); + } + } + } + return mucServers; + } + + public void sendMessagePacket(Account account, MessagePacket packet) { + XmppConnection connection = account.getXmppConnection(); + if (connection != null) { + connection.sendMessagePacket(packet); + } + } + + public void sendPresencePacket(Account account, PresencePacket packet) { + XmppConnection connection = account.getXmppConnection(); + if (connection != null) { + connection.sendPresencePacket(packet); + } + } + + public void sendCreateAccountWithCaptchaPacket(Account account, String id, Data data) { + final XmppConnection connection = account.getXmppConnection(); + if (connection != null) { + IqPacket request = mIqGenerator.generateCreateAccountWithCaptcha(account, id, data); + connection.sendUnmodifiedIqPacket(request, connection.registrationResponseListener); + } + } + + public void sendIqPacket(final Account account, final IqPacket packet, final OnIqPacketReceived callback) { + final XmppConnection connection = account.getXmppConnection(); + if (connection != null) { + connection.sendIqPacket(packet, callback); + } + } + + public void sendPresence(final Account account) { + sendPresence(account, checkListeners() && broadcastLastActivity()); + } + + private void sendPresence(final Account account, final boolean includeIdleTimestamp) { + PresencePacket packet; + if (manuallyChangePresence()) { + packet = mPresenceGenerator.selfPresence(account, account.getPresenceStatus()); + String message = account.getPresenceStatusMessage(); + if (message != null && !message.isEmpty()) { + packet.addChild(new Element("status").setContent(message)); + } + } else { + packet = mPresenceGenerator.selfPresence(account, getTargetPresence()); + } + if (mLastActivity > 0 && includeIdleTimestamp) { + long since = Math.min(mLastActivity, System.currentTimeMillis()); //don't send future dates + packet.addChild("idle", "urn:xmpp:idle:1").setAttribute("since", AbstractGenerator.getTimestamp(since)); + } + sendPresencePacket(account, packet); + } + + private void deactivateGracePeriod() { + for (Account account : getAccounts()) { + account.deactivateGracePeriod(); + } + } + + public void refreshAllPresences() { + boolean includeIdleTimestamp = checkListeners() && broadcastLastActivity(); + for (Account account : getAccounts()) { + if (!account.isOptionSet(Account.OPTION_DISABLED)) { + sendPresence(account, includeIdleTimestamp); + } + } + } + + private void refreshAllGcmTokens() { + for (Account account : getAccounts()) { + if (account.isOnlineAndConnected() && mPushManagementService.available(account)) { + mPushManagementService.registerPushTokenOnServer(account); + } + } + } + + private void sendOfflinePresence(final Account account) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": sending offline presence"); + sendPresencePacket(account, mPresenceGenerator.sendOfflinePresence(account)); + } + + public MessageGenerator getMessageGenerator() { + return this.mMessageGenerator; + } + + public PresenceGenerator getPresenceGenerator() { + return this.mPresenceGenerator; + } + + public IqGenerator getIqGenerator() { + return this.mIqGenerator; + } + + public IqParser getIqParser() { + return this.mIqParser; + } + + public JingleConnectionManager getJingleConnectionManager() { + return this.mJingleConnectionManager; + } + + public MessageArchiveService getMessageArchiveService() { + return this.mMessageArchiveService; + } + + public List findContacts(Jid jid) { + ArrayList contacts = new ArrayList<>(); + for (Account account : getAccounts()) { + if (!account.isOptionSet(Account.OPTION_DISABLED)) { + Contact contact = account.getRoster().getContactFromRoster(jid); + if (contact != null) { + contacts.add(contact); + } + } + } + return contacts; + } + + public Conversation findFirstMuc(Jid jid) { + for (Conversation conversation : getConversations()) { + if (conversation.getJid().toBareJid().equals(jid.toBareJid()) + && conversation.getMode() == Conversation.MODE_MULTI) { + return conversation; + } + } + return null; + } + + public NotificationService getNotificationService() { + return this.mNotificationService; + } + + public HttpConnectionManager getHttpConnectionManager() { + return this.mHttpConnectionManager; + } + + public void resendFailedMessages(final Message message) { + final Collection messages = new ArrayList<>(); + Message current = message; + while (current.getStatus() == Message.STATUS_SEND_FAILED) { + messages.add(current); + if (current.mergeable(current.next())) { + current = current.next(); + } else { + break; + } + } + for (final Message msg : messages) { + msg.setTime(System.currentTimeMillis()); + markMessage(msg, Message.STATUS_WAITING); + this.resendMessage(msg, false); + } + } + + public void clearConversationHistory(final Conversation conversation) { + conversation.clearMessages(); + conversation.setHasMessagesLeftOnServer(false); //avoid messages getting loaded through mam + conversation.setLastClearHistory(System.currentTimeMillis()); + Runnable runnable = new Runnable() { + @Override + public void run() { + databaseBackend.deleteMessagesInConversation(conversation); + databaseBackend.updateConversation(conversation); + + } + }; + mDatabaseExecutor.execute(runnable); + } + + public void sendBlockRequest(final Blockable blockable, boolean reportSpam) { + if (blockable != null && blockable.getBlockedJid() != null) { + final Jid jid = blockable.getBlockedJid(); + this.sendIqPacket(blockable.getAccount(), getIqGenerator().generateSetBlockRequest(jid, reportSpam), new OnIqPacketReceived() { + + @Override + public void onIqPacketReceived(final Account account, final IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.RESULT) { + account.getBlocklist().add(jid); + updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED); + } + } + }); + } + } + + public void sendUnblockRequest(final Blockable blockable) { + if (blockable != null && blockable.getJid() != null) { + final Jid jid = blockable.getBlockedJid(); + this.sendIqPacket(blockable.getAccount(), getIqGenerator().generateSetUnblockRequest(jid), new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(final Account account, final IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.RESULT) { + account.getBlocklist().remove(jid); + updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED); + } + } + }); + } + } + + public void publishDisplayName(Account account) { + String displayName = account.getDisplayName(); + if (displayName != null && !displayName.isEmpty()) { + IqPacket publish = mIqGenerator.publishNick(displayName); + sendIqPacket(account, publish, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.ERROR) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not publish nick"); + } + } + }); + } + } + + public ServiceDiscoveryResult getCachedServiceDiscoveryResult(Pair key) { + ServiceDiscoveryResult result = discoCache.get(key); + if (result != null) { + return result; + } else { + result = databaseBackend.findDiscoveryResult(key.first, key.second); + if (result != null) { + discoCache.put(key, result); + } + return result; + } + } + + public void fetchCaps(Account account, final Jid jid, final Presence presence) { + final Pair key = new Pair<>(presence.getHash(), presence.getVer()); + ServiceDiscoveryResult disco = getCachedServiceDiscoveryResult(key); + if (disco != null) { + presence.setServiceDiscoveryResult(disco); + } else { + if (!account.inProgressDiscoFetches.contains(key)) { + account.inProgressDiscoFetches.add(key); + IqPacket request = new IqPacket(IqPacket.TYPE.GET); + request.setTo(jid); + request.query("http://jabber.org/protocol/disco#info"); + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": making disco request for " + key.second + " to " + jid); + sendIqPacket(account, request, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket discoPacket) { + if (discoPacket.getType() == IqPacket.TYPE.RESULT) { + ServiceDiscoveryResult disco = new ServiceDiscoveryResult(discoPacket); + if (presence.getVer().equals(disco.getVer())) { + databaseBackend.insertDiscoveryResult(disco); + injectServiceDiscorveryResult(account.getRoster(), presence.getHash(), presence.getVer(), disco); + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": mismatch in caps for contact " + jid + " " + presence.getVer() + " vs " + disco.getVer()); + } + } + account.inProgressDiscoFetches.remove(key); + } + }); + } + } + } + + private void injectServiceDiscorveryResult(Roster roster, String hash, String ver, ServiceDiscoveryResult disco) { + for (Contact contact : roster.getContacts()) { + for (Presence presence : contact.getPresences().getPresences().values()) { + if (hash.equals(presence.getHash()) && ver.equals(presence.getVer())) { + presence.setServiceDiscoveryResult(disco); + } + } + } + } + + public void fetchMamPreferences(Account account, final OnMamPreferencesFetched callback) { + IqPacket request = new IqPacket(IqPacket.TYPE.GET); + request.addChild("prefs", "urn:xmpp:mam:0"); + sendIqPacket(account, request, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + Element prefs = packet.findChild("prefs", "urn:xmpp:mam:0"); + if (packet.getType() == IqPacket.TYPE.RESULT && prefs != null) { + callback.onPreferencesFetched(prefs); + } else { + callback.onPreferencesFetchFailed(); + } + } + }); + } + + public PushManagementService getPushManagementService() { + return mPushManagementService; + } + + public Account getPendingAccount() { + Account pending = null; + for (Account account : getAccounts()) { + if (account.isOptionSet(Account.OPTION_REGISTER)) { + pending = account; + } else { + return null; + } + } + return pending; + } + + public void changeStatus(Account account, Presence.Status status, String statusMessage, boolean send) { + if (!statusMessage.isEmpty()) { + databaseBackend.insertPresenceTemplate(new PresenceTemplate(status, statusMessage)); + } + changeStatusReal(account, status, statusMessage, send); + } + + private void changeStatusReal(Account account, Presence.Status status, String statusMessage, boolean send) { + account.setPresenceStatus(status); + account.setPresenceStatusMessage(statusMessage); + databaseBackend.updateAccount(account); + if (!account.isOptionSet(Account.OPTION_DISABLED) && send) { + sendPresence(account); + } + } + + public void changeStatus(Presence.Status status, String statusMessage) { + if (!statusMessage.isEmpty()) { + databaseBackend.insertPresenceTemplate(new PresenceTemplate(status, statusMessage)); + } + for (Account account : getAccounts()) { + changeStatusReal(account, status, statusMessage, true); + } + } + + public List getPresenceTemplates(Account account) { + List templates = databaseBackend.getPresenceTemplates(); + for (PresenceTemplate template : account.getSelfContact().getPresences().asTemplates()) { + if (!templates.contains(template)) { + templates.add(0, template); + } + } + return templates; + } + + public void saveConversationAsBookmark(Conversation conversation, String name) { + Account account = conversation.getAccount(); + Bookmark bookmark = new Bookmark(account, conversation.getJid().toBareJid()); + if (!conversation.getJid().isBareJid()) { + bookmark.setNick(conversation.getJid().getResourcepart()); + } + if (name != null && !name.trim().isEmpty()) { + bookmark.setBookmarkName(name.trim()); + } + bookmark.setAutojoin(getPreferences().getBoolean("autojoin", true)); + account.getBookmarks().add(bookmark); + pushBookmarks(account); + conversation.setBookmark(bookmark); + } + + public void clearStartTimeCounter() { + mDatabaseExecutor.execute(new Runnable() { + @Override + public void run() { + databaseBackend.clearStartTimeCounter(); + } + }); + } + + public void verifyFingerprints(Contact contact, List fingerprints) { + boolean needsRosterWrite = false; + final AxolotlService axolotlService = contact.getAccount().getAxolotlService(); + for (XmppUri.Fingerprint fp : fingerprints) { + if (fp.type == XmppUri.FingerprintType.OTR) { + needsRosterWrite |= contact.addOtrFingerprint(fp.fingerprint); + } else if (fp.type == XmppUri.FingerprintType.OMEMO) { + String fingerprint = "05" + fp.fingerprint.replaceAll("\\s", ""); + FingerprintStatus fingerprintStatus = axolotlService.getFingerprintTrust(fingerprint); + if (fingerprintStatus != null) { + if (!fingerprintStatus.isVerified()) { + axolotlService.setFingerprintTrust(fingerprint, fingerprintStatus.toVerified()); + } + } else { + axolotlService.preVerifyFingerprint(contact, fingerprint); + } + } + } + if (needsRosterWrite) { + syncRosterToDisk(contact.getAccount()); + } + } + + public boolean verifyFingerprints(Account account, List fingerprints) { + final AxolotlService axolotlService = account.getAxolotlService(); + boolean verifiedSomething = false; + for (XmppUri.Fingerprint fp : fingerprints) { + if (fp.type == XmppUri.FingerprintType.OMEMO) { + String fingerprint = "05" + fp.fingerprint.replaceAll("\\s", ""); + Log.d(Config.LOGTAG, "trying to verify own fp=" + fingerprint); + FingerprintStatus fingerprintStatus = axolotlService.getFingerprintTrust(fingerprint); + if (fingerprintStatus != null) { + if (!fingerprintStatus.isVerified()) { + axolotlService.setFingerprintTrust(fingerprint, fingerprintStatus.toVerified()); + verifiedSomething = true; + } + } else { + axolotlService.preVerifyFingerprint(account, fingerprint); + verifiedSomething = true; + } + } + } + return verifiedSomething; + } + + public interface OnMamPreferencesFetched { + void onPreferencesFetched(Element prefs); + + void onPreferencesFetchFailed(); + } + + public void pushMamPreferences(Account account, Element prefs) { + IqPacket set = new IqPacket(IqPacket.TYPE.SET); + set.addChild(prefs); + sendIqPacket(account, set, null); + } + + public interface OnAccountCreated { + void onAccountCreated(Account account); + + void informUser(int r); + } + + public interface OnMoreMessagesLoaded { + void onMoreMessagesLoaded(int count, Conversation conversation); + + void informUser(int r); + } + + public interface OnAccountPasswordChanged { + void onPasswordChangeSucceeded(); + + void onPasswordChangeFailed(); + } + + public interface OnAffiliationChanged { + void onAffiliationChangedSuccessful(Jid jid); + + void onAffiliationChangeFailed(Jid jid, int resId); + } + + public interface OnRoleChanged { + void onRoleChangedSuccessful(String nick); + + void onRoleChangeFailed(String nick, int resid); + } + + public interface OnConversationUpdate { + void onConversationUpdate(); + } + + public interface OnAccountUpdate { + void onAccountUpdate(); + } + + public interface OnCaptchaRequested { + void onCaptchaRequested(Account account, + String id, + Data data, + Bitmap captcha); + } + + public interface OnRosterUpdate { + void onRosterUpdate(); + } + + public interface OnMucRosterUpdate { + void onMucRosterUpdate(); + } + + public interface OnConferenceConfigurationFetched { + void onConferenceConfigurationFetched(Conversation conversation); + + void onFetchFailed(Conversation conversation, Element error); + } + + public interface OnConferenceJoined { + void onConferenceJoined(Conversation conversation); + } + + public interface OnConferenceOptionsPushed { + void onPushSucceeded(); + + void onPushFailed(); + } + + public interface OnShowErrorToast { + void onShowErrorToast(int resId); + } + + public class XmppConnectionBinder extends Binder { + public XmppConnectionService getService() { + return XmppConnectionService.this; + } + } + + public void ScheduleAutomaticExport() { + //start export log service every day at given time + if (Config.ExportLogs) { + if (Config.ExportLogs_Hour >= 0 && Config.ExportLogs_Hour <= 23 && Config.ExportLogs_Minute >= 0 && Config.ExportLogs_Minute <= 59) { + Log.d(Config.LOGTAG, "Schedule automatic export logs at " + Config.ExportLogs_Hour + ":" + Config.ExportLogs_Minute); + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(System.currentTimeMillis()); + calendar.set(Calendar.HOUR_OF_DAY, Config.ExportLogs_Hour); + calendar.set(Calendar.MINUTE, Config.ExportLogs_Minute); + Intent intent = new Intent(this, AlarmReceiver.class); + intent.setAction("exportlogs"); + final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, AlarmReceiver.SCHEDULE_ALARM_REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT); + ((AlarmManager) getSystemService(ALARM_SERVICE)).setInexactRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), AlarmManager.INTERVAL_DAY, pendingIntent); + } + } + } + + public void CancelAutomaticExport() { + if (Config.ExportLogs) { + Log.d(Config.LOGTAG, "Cancel scheduled automatic export"); + Intent intent = new Intent(this, AlarmReceiver.class); + final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, AlarmReceiver.SCHEDULE_ALARM_REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT); + ((AlarmManager) this.getSystemService(ALARM_SERVICE)).cancel(pendingIntent); + } + } } diff --git a/src/main/java/de/pixart/messenger/ui/AboutPreference.java b/src/main/java/de/pixart/messenger/ui/AboutPreference.java index d4cfa982b..239ef53fb 100644 --- a/src/main/java/de/pixart/messenger/ui/AboutPreference.java +++ b/src/main/java/de/pixart/messenger/ui/AboutPreference.java @@ -8,15 +8,15 @@ import android.util.AttributeSet; import de.pixart.messenger.utils.PhoneHelper; public class AboutPreference extends Preference { - public AboutPreference(final Context context, final AttributeSet attrs, final int defStyle) { - super(context, attrs, defStyle); - setSummary(); - } + public AboutPreference(final Context context, final AttributeSet attrs, final int defStyle) { + super(context, attrs, defStyle); + setSummary(); + } - public AboutPreference(final Context context, final AttributeSet attrs) { - super(context, attrs); - setSummary(); - } + public AboutPreference(final Context context, final AttributeSet attrs) { + super(context, attrs); + setSummary(); + } @Override protected void onClick() { @@ -26,7 +26,7 @@ public class AboutPreference extends Preference { } private void setSummary() { - setSummary("Pix-Art Messenger " + PhoneHelper.getVersionName(getContext())); - } + setSummary("Pix-Art Messenger " + PhoneHelper.getVersionName(getContext())); + } } diff --git a/src/main/java/de/pixart/messenger/ui/AbstractSearchableListItemActivity.java b/src/main/java/de/pixart/messenger/ui/AbstractSearchableListItemActivity.java index 9b12e43ef..768e738ba 100644 --- a/src/main/java/de/pixart/messenger/ui/AbstractSearchableListItemActivity.java +++ b/src/main/java/de/pixart/messenger/ui/AbstractSearchableListItemActivity.java @@ -20,105 +20,105 @@ import de.pixart.messenger.entities.ListItem; import de.pixart.messenger.ui.adapter.ListItemAdapter; public abstract class AbstractSearchableListItemActivity extends XmppActivity { - private ListView mListView; - private final List listItems = new ArrayList<>(); - private ArrayAdapter mListItemsAdapter; - - private EditText mSearchEditText; - - private final MenuItem.OnActionExpandListener mOnActionExpandListener = new MenuItem.OnActionExpandListener() { - - @Override - public boolean onMenuItemActionExpand(final MenuItem item) { - mSearchEditText.post(new Runnable() { - - @Override - public void run() { - mSearchEditText.requestFocus(); - final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - imm.showSoftInput(mSearchEditText, - InputMethodManager.SHOW_IMPLICIT); - } - }); - - return true; - } - - @Override - public boolean onMenuItemActionCollapse(final MenuItem item) { - final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(mSearchEditText.getWindowToken(), - InputMethodManager.HIDE_IMPLICIT_ONLY); - mSearchEditText.setText(""); - filterContacts(); - return true; - } - }; - - private final TextWatcher mSearchTextWatcher = new TextWatcher() { - - @Override - public void afterTextChanged(final Editable editable) { - filterContacts(editable.toString()); - } - - @Override - public void beforeTextChanged(final CharSequence s, final int start, final int count, - final int after) { - } - - @Override - public void onTextChanged(final CharSequence s, final int start, final int before, - final int count) { - } - }; - - public ListView getListView() { - return mListView; - } - - public List getListItems() { - return listItems; - } - - public EditText getSearchEditText() { - return mSearchEditText; - } - - public ArrayAdapter getListItemAdapter() { - return mListItemsAdapter; - } - - @Override - public void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_choose_contact); - mListView = (ListView) findViewById(R.id.choose_contact_list); - mListView.setFastScrollEnabled(true); - mListItemsAdapter = new ListItemAdapter(this, listItems); - mListView.setAdapter(mListItemsAdapter); - } - - @Override - public boolean onCreateOptionsMenu(final Menu menu) { - getMenuInflater().inflate(R.menu.choose_contact, menu); - final MenuItem menuSearchView = menu.findItem(R.id.action_search); - final View mSearchView = menuSearchView.getActionView(); - mSearchEditText = (EditText) mSearchView - .findViewById(R.id.search_field); - mSearchEditText.addTextChangedListener(mSearchTextWatcher); - menuSearchView.setOnActionExpandListener(mOnActionExpandListener); - return true; - } - - protected void filterContacts() { - filterContacts(null); - } - - protected abstract void filterContacts(final String needle); - - @Override - void onBackendConnected() { - filterContacts(); - } + private ListView mListView; + private final List listItems = new ArrayList<>(); + private ArrayAdapter mListItemsAdapter; + + private EditText mSearchEditText; + + private final MenuItem.OnActionExpandListener mOnActionExpandListener = new MenuItem.OnActionExpandListener() { + + @Override + public boolean onMenuItemActionExpand(final MenuItem item) { + mSearchEditText.post(new Runnable() { + + @Override + public void run() { + mSearchEditText.requestFocus(); + final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(mSearchEditText, + InputMethodManager.SHOW_IMPLICIT); + } + }); + + return true; + } + + @Override + public boolean onMenuItemActionCollapse(final MenuItem item) { + final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(mSearchEditText.getWindowToken(), + InputMethodManager.HIDE_IMPLICIT_ONLY); + mSearchEditText.setText(""); + filterContacts(); + return true; + } + }; + + private final TextWatcher mSearchTextWatcher = new TextWatcher() { + + @Override + public void afterTextChanged(final Editable editable) { + filterContacts(editable.toString()); + } + + @Override + public void beforeTextChanged(final CharSequence s, final int start, final int count, + final int after) { + } + + @Override + public void onTextChanged(final CharSequence s, final int start, final int before, + final int count) { + } + }; + + public ListView getListView() { + return mListView; + } + + public List getListItems() { + return listItems; + } + + public EditText getSearchEditText() { + return mSearchEditText; + } + + public ArrayAdapter getListItemAdapter() { + return mListItemsAdapter; + } + + @Override + public void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_choose_contact); + mListView = (ListView) findViewById(R.id.choose_contact_list); + mListView.setFastScrollEnabled(true); + mListItemsAdapter = new ListItemAdapter(this, listItems); + mListView.setAdapter(mListItemsAdapter); + } + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + getMenuInflater().inflate(R.menu.choose_contact, menu); + final MenuItem menuSearchView = menu.findItem(R.id.action_search); + final View mSearchView = menuSearchView.getActionView(); + mSearchEditText = (EditText) mSearchView + .findViewById(R.id.search_field); + mSearchEditText.addTextChangedListener(mSearchTextWatcher); + menuSearchView.setOnActionExpandListener(mOnActionExpandListener); + return true; + } + + protected void filterContacts() { + filterContacts(null); + } + + protected abstract void filterContacts(final String needle); + + @Override + void onBackendConnected() { + filterContacts(); + } } diff --git a/src/main/java/de/pixart/messenger/ui/BlockContactDialog.java b/src/main/java/de/pixart/messenger/ui/BlockContactDialog.java index f72a365b9..d1c6b56d6 100644 --- a/src/main/java/de/pixart/messenger/ui/BlockContactDialog.java +++ b/src/main/java/de/pixart/messenger/ui/BlockContactDialog.java @@ -17,48 +17,48 @@ import de.pixart.messenger.entities.Blockable; import de.pixart.messenger.services.XmppConnectionService; public final class BlockContactDialog { - public static void show(final Context context, - final XmppConnectionService xmppConnectionService, - final Blockable blockable) { - final AlertDialog.Builder builder = new AlertDialog.Builder(context); - final boolean isBlocked = blockable.isBlocked(); - builder.setNegativeButton(R.string.cancel, null); - LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - LinearLayout view = (LinearLayout) inflater.inflate(R.layout.dialog_block_contact,null); - TextView message = (TextView) view.findViewById(R.id.text); - final CheckBox report = (CheckBox) view.findViewById(R.id.report_spam); - final boolean reporting = blockable.getAccount().getXmppConnection().getFeatures().spamReporting(); - report.setVisibility(!isBlocked && reporting ? View.VISIBLE : View.GONE); - builder.setView(view); + public static void show(final Context context, + final XmppConnectionService xmppConnectionService, + final Blockable blockable) { + final AlertDialog.Builder builder = new AlertDialog.Builder(context); + final boolean isBlocked = blockable.isBlocked(); + builder.setNegativeButton(R.string.cancel, null); + LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + LinearLayout view = (LinearLayout) inflater.inflate(R.layout.dialog_block_contact, null); + TextView message = (TextView) view.findViewById(R.id.text); + final CheckBox report = (CheckBox) view.findViewById(R.id.report_spam); + final boolean reporting = blockable.getAccount().getXmppConnection().getFeatures().spamReporting(); + report.setVisibility(!isBlocked && reporting ? View.VISIBLE : View.GONE); + builder.setView(view); - String value; - SpannableString spannable; - if (blockable.getJid().isDomainJid() || blockable.getAccount().isBlocked(blockable.getJid().toDomainJid())) { - builder.setTitle(isBlocked ? R.string.action_unblock_domain : R.string.action_block_domain); - value = blockable.getJid().toDomainJid().toString(); - spannable = new SpannableString(context.getString(isBlocked ? R.string.unblock_domain_text : R.string.block_domain_text, value)); - message.setText(spannable); - } else { - builder.setTitle(isBlocked ? R.string.action_unblock_contact : R.string.action_block_contact); - value = blockable.getJid().toBareJid().toString(); - spannable = new SpannableString(context.getString(isBlocked ? R.string.unblock_contact_text : R.string.block_contact_text, value)); - } - int start = spannable.toString().indexOf(value); - if (start >= 0) { - spannable.setSpan(new TypefaceSpan("monospace"),start,start + value.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - message.setText(spannable); - builder.setPositiveButton(isBlocked ? R.string.unblock : R.string.block, new DialogInterface.OnClickListener() { + String value; + SpannableString spannable; + if (blockable.getJid().isDomainJid() || blockable.getAccount().isBlocked(blockable.getJid().toDomainJid())) { + builder.setTitle(isBlocked ? R.string.action_unblock_domain : R.string.action_block_domain); + value = blockable.getJid().toDomainJid().toString(); + spannable = new SpannableString(context.getString(isBlocked ? R.string.unblock_domain_text : R.string.block_domain_text, value)); + message.setText(spannable); + } else { + builder.setTitle(isBlocked ? R.string.action_unblock_contact : R.string.action_block_contact); + value = blockable.getJid().toBareJid().toString(); + spannable = new SpannableString(context.getString(isBlocked ? R.string.unblock_contact_text : R.string.block_contact_text, value)); + } + int start = spannable.toString().indexOf(value); + if (start >= 0) { + spannable.setSpan(new TypefaceSpan("monospace"), start, start + value.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + message.setText(spannable); + builder.setPositiveButton(isBlocked ? R.string.unblock : R.string.block, new DialogInterface.OnClickListener() { - @Override - public void onClick(final DialogInterface dialog, final int which) { - if (isBlocked) { - xmppConnectionService.sendUnblockRequest(blockable); - } else { - xmppConnectionService.sendBlockRequest(blockable, report.isChecked()); - } - } - }); - builder.create().show(); - } + @Override + public void onClick(final DialogInterface dialog, final int which) { + if (isBlocked) { + xmppConnectionService.sendUnblockRequest(blockable); + } else { + xmppConnectionService.sendBlockRequest(blockable, report.isChecked()); + } + } + }); + builder.create().show(); + } } diff --git a/src/main/java/de/pixart/messenger/ui/BlocklistActivity.java b/src/main/java/de/pixart/messenger/ui/BlocklistActivity.java index cefa17de6..cb5a0112b 100644 --- a/src/main/java/de/pixart/messenger/ui/BlocklistActivity.java +++ b/src/main/java/de/pixart/messenger/ui/BlocklistActivity.java @@ -14,61 +14,61 @@ import de.pixart.messenger.xmpp.jid.Jid; public class BlocklistActivity extends AbstractSearchableListItemActivity implements OnUpdateBlocklist { - private Account account = null; + private Account account = null; - @Override - public void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getListView().setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { + @Override + public void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getListView().setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { - @Override - public boolean onItemLongClick(final AdapterView parent, - final View view, - final int position, - final long id) { - BlockContactDialog.show(parent.getContext(), xmppConnectionService,(Contact) getListItems().get(position)); - return true; - } - }); - } + @Override + public boolean onItemLongClick(final AdapterView parent, + final View view, + final int position, + final long id) { + BlockContactDialog.show(parent.getContext(), xmppConnectionService, (Contact) getListItems().get(position)); + return true; + } + }); + } - @Override - public void onBackendConnected() { - for (final Account account : xmppConnectionService.getAccounts()) { - if (account.getJid().toString().equals(getIntent().getStringExtra(EXTRA_ACCOUNT))) { - this.account = account; - break; - } - } - filterContacts(); - } + @Override + public void onBackendConnected() { + for (final Account account : xmppConnectionService.getAccounts()) { + if (account.getJid().toString().equals(getIntent().getStringExtra(EXTRA_ACCOUNT))) { + this.account = account; + break; + } + } + filterContacts(); + } - @Override - protected void filterContacts(final String needle) { - getListItems().clear(); - if (account != null) { - for (final Jid jid : account.getBlocklist()) { - final Contact contact = account.getRoster().getContact(jid); - if (contact.match(this, needle) && contact.isBlocked()) { - getListItems().add(contact); - } - } - Collections.sort(getListItems()); - } - getListItemAdapter().notifyDataSetChanged(); - } + @Override + protected void filterContacts(final String needle) { + getListItems().clear(); + if (account != null) { + for (final Jid jid : account.getBlocklist()) { + final Contact contact = account.getRoster().getContact(jid); + if (contact.match(this, needle) && contact.isBlocked()) { + getListItems().add(contact); + } + } + Collections.sort(getListItems()); + } + getListItemAdapter().notifyDataSetChanged(); + } - protected void refreshUiReal() { - final Editable editable = getSearchEditText().getText(); - if (editable != null) { - filterContacts(editable.toString()); - } else { - filterContacts(); - } - } + protected void refreshUiReal() { + final Editable editable = getSearchEditText().getText(); + if (editable != null) { + filterContacts(editable.toString()); + } else { + filterContacts(); + } + } - @Override - public void OnUpdateBlocklist(final OnUpdateBlocklist.Status status) { - refreshUi(); - } + @Override + public void OnUpdateBlocklist(final OnUpdateBlocklist.Status status) { + refreshUi(); + } } diff --git a/src/main/java/de/pixart/messenger/ui/ChangePasswordActivity.java b/src/main/java/de/pixart/messenger/ui/ChangePasswordActivity.java index 361caf182..ddbbbca36 100644 --- a/src/main/java/de/pixart/messenger/ui/ChangePasswordActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ChangePasswordActivity.java @@ -14,109 +14,109 @@ import de.pixart.messenger.services.XmppConnectionService; public class ChangePasswordActivity extends XmppActivity implements XmppConnectionService.OnAccountPasswordChanged { - private Button mChangePasswordButton; - private View.OnClickListener mOnChangePasswordButtonClicked = new View.OnClickListener() { - @Override - public void onClick(View view) { - if (mAccount != null) { - final String currentPassword = mCurrentPassword.getText().toString(); - final String newPassword = mNewPassword.getText().toString(); - final String newPasswordConfirm = mNewPasswordConfirm.getText().toString(); - if (!mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) && !currentPassword.equals(mAccount.getPassword())) { - mCurrentPassword.requestFocus(); - mCurrentPassword.setError(getString(R.string.account_status_unauthorized)); - } else if (!newPassword.equals(newPasswordConfirm)) { - mNewPasswordConfirm.requestFocus(); - mNewPasswordConfirm.setError(getString(R.string.passwords_do_not_match)); - } else if (newPassword.trim().isEmpty()) { - mNewPassword.requestFocus(); - mNewPassword.setError(getString(R.string.password_should_not_be_empty)); - } else { - mCurrentPassword.setError(null); - mNewPassword.setError(null); - mNewPasswordConfirm.setError(null); - xmppConnectionService.updateAccountPasswordOnServer(mAccount, newPassword, ChangePasswordActivity.this); - mChangePasswordButton.setEnabled(false); - mChangePasswordButton.setTextColor(getSecondaryTextColor()); - mChangePasswordButton.setText(R.string.updating); - } - } - } - }; - private TextView mCurrentPasswordLabel; - private EditText mCurrentPassword; - private EditText mNewPassword; - private EditText mNewPasswordConfirm; - private Account mAccount; + private Button mChangePasswordButton; + private View.OnClickListener mOnChangePasswordButtonClicked = new View.OnClickListener() { + @Override + public void onClick(View view) { + if (mAccount != null) { + final String currentPassword = mCurrentPassword.getText().toString(); + final String newPassword = mNewPassword.getText().toString(); + final String newPasswordConfirm = mNewPasswordConfirm.getText().toString(); + if (!mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) && !currentPassword.equals(mAccount.getPassword())) { + mCurrentPassword.requestFocus(); + mCurrentPassword.setError(getString(R.string.account_status_unauthorized)); + } else if (!newPassword.equals(newPasswordConfirm)) { + mNewPasswordConfirm.requestFocus(); + mNewPasswordConfirm.setError(getString(R.string.passwords_do_not_match)); + } else if (newPassword.trim().isEmpty()) { + mNewPassword.requestFocus(); + mNewPassword.setError(getString(R.string.password_should_not_be_empty)); + } else { + mCurrentPassword.setError(null); + mNewPassword.setError(null); + mNewPasswordConfirm.setError(null); + xmppConnectionService.updateAccountPasswordOnServer(mAccount, newPassword, ChangePasswordActivity.this); + mChangePasswordButton.setEnabled(false); + mChangePasswordButton.setTextColor(getSecondaryTextColor()); + mChangePasswordButton.setText(R.string.updating); + } + } + } + }; + private TextView mCurrentPasswordLabel; + private EditText mCurrentPassword; + private EditText mNewPassword; + private EditText mNewPasswordConfirm; + private Account mAccount; - @Override - void onBackendConnected() { - this.mAccount = extractAccount(getIntent()); - if (this.mAccount != null && this.mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)) { - this.mCurrentPasswordLabel.setVisibility(View.GONE); - this.mCurrentPassword.setVisibility(View.GONE); - } else { - this.mCurrentPasswordLabel.setVisibility(View.VISIBLE); - this.mCurrentPassword.setVisibility(View.VISIBLE); - } - } + @Override + void onBackendConnected() { + this.mAccount = extractAccount(getIntent()); + if (this.mAccount != null && this.mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)) { + this.mCurrentPasswordLabel.setVisibility(View.GONE); + this.mCurrentPassword.setVisibility(View.GONE); + } else { + this.mCurrentPasswordLabel.setVisibility(View.VISIBLE); + this.mCurrentPassword.setVisibility(View.VISIBLE); + } + } - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_change_password); - Button mCancelButton = (Button) findViewById(R.id.left_button); - mCancelButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - finish(); - } - }); - this.mChangePasswordButton = (Button) findViewById(R.id.right_button); - this.mChangePasswordButton.setOnClickListener(this.mOnChangePasswordButtonClicked); - this.mCurrentPasswordLabel = (TextView) findViewById(R.id.current_password_label); - this.mCurrentPassword = (EditText) findViewById(R.id.current_password); - this.mNewPassword = (EditText) findViewById(R.id.new_password); - this.mNewPasswordConfirm = (EditText) findViewById(R.id.new_password_confirm); - } + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_change_password); + Button mCancelButton = (Button) findViewById(R.id.left_button); + mCancelButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + finish(); + } + }); + this.mChangePasswordButton = (Button) findViewById(R.id.right_button); + this.mChangePasswordButton.setOnClickListener(this.mOnChangePasswordButtonClicked); + this.mCurrentPasswordLabel = (TextView) findViewById(R.id.current_password_label); + this.mCurrentPassword = (EditText) findViewById(R.id.current_password); + this.mNewPassword = (EditText) findViewById(R.id.new_password); + this.mNewPasswordConfirm = (EditText) findViewById(R.id.new_password_confirm); + } - @Override - protected void onStart() { - super.onStart(); - Intent intent = getIntent(); - String password = intent != null ? intent.getStringExtra("password") : null; - if (password != null) { - this.mNewPassword.getEditableText().clear(); - this.mNewPassword.getEditableText().append(password); - } - } + @Override + protected void onStart() { + super.onStart(); + Intent intent = getIntent(); + String password = intent != null ? intent.getStringExtra("password") : null; + if (password != null) { + this.mNewPassword.getEditableText().clear(); + this.mNewPassword.getEditableText().append(password); + } + } - @Override - public void onPasswordChangeSucceeded() { - runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(ChangePasswordActivity.this,R.string.password_changed,Toast.LENGTH_LONG).show(); - finish(); - } - }); - } + @Override + public void onPasswordChangeSucceeded() { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(ChangePasswordActivity.this, R.string.password_changed, Toast.LENGTH_LONG).show(); + finish(); + } + }); + } - @Override - public void onPasswordChangeFailed() { - runOnUiThread(new Runnable() { - @Override - public void run() { - mNewPassword.setError(getString(R.string.could_not_change_password)); - mChangePasswordButton.setEnabled(true); - mChangePasswordButton.setTextColor(getPrimaryTextColor()); - mChangePasswordButton.setText(R.string.change_password); - } - }); + @Override + public void onPasswordChangeFailed() { + runOnUiThread(new Runnable() { + @Override + public void run() { + mNewPassword.setError(getString(R.string.could_not_change_password)); + mChangePasswordButton.setEnabled(true); + mChangePasswordButton.setTextColor(getPrimaryTextColor()); + mChangePasswordButton.setText(R.string.change_password); + } + }); - } + } - public void refreshUiReal() { + public void refreshUiReal() { - } + } } \ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/ChooseContactActivity.java b/src/main/java/de/pixart/messenger/ui/ChooseContactActivity.java index e38433893..b1dd48a5f 100644 --- a/src/main/java/de/pixart/messenger/ui/ChooseContactActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ChooseContactActivity.java @@ -29,218 +29,218 @@ import de.pixart.messenger.entities.ListItem; import de.pixart.messenger.xmpp.jid.Jid; public class ChooseContactActivity extends AbstractSearchableListItemActivity { - private List mActivatedAccounts = new ArrayList(); - private List mKnownHosts; - - private Set selected; - private Set filterContacts; - public static final String EXTRA_TITLE_RES_ID = "extra_title_res_id"; - - @Override - public void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - filterContacts = new HashSet<>(); - String[] contacts = getIntent().getStringArrayExtra("filter_contacts"); - if (contacts != null) { - Collections.addAll(filterContacts, contacts); - } - - if (getIntent().getBooleanExtra("multiple", false)) { - getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); - getListView().setMultiChoiceModeListener(new MultiChoiceModeListener() { - - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - return false; - } - - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(getSearchEditText().getWindowToken(), - InputMethodManager.HIDE_IMPLICIT_ONLY); - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.select_multiple, menu); - selected = new HashSet(); - return true; - } - - @Override - public void onDestroyActionMode(ActionMode mode) { - } - - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - switch(item.getItemId()) { - case R.id.selection_submit: - final Intent request = getIntent(); - final Intent data = new Intent(); - data.putExtra("conversation", - request.getStringExtra("conversation")); - String[] selection = getSelectedContactJids(); - data.putExtra("contacts", selection); - data.putExtra("multiple", true); - data.putExtra(EXTRA_ACCOUNT,request.getStringExtra(EXTRA_ACCOUNT)); - data.putExtra("subject", request.getStringExtra("subject")); - setResult(RESULT_OK, data); - finish(); - return true; - } - return false; - } - - @Override - public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { - Contact item = (Contact) getListItems().get(position); - if (checked) { - selected.add(item); - } else { - selected.remove(item); - } - int numSelected = selected.size(); - MenuItem selectButton = mode.getMenu().findItem(R.id.selection_submit); - String buttonText = getResources().getQuantityString(R.plurals.select_contact, - numSelected, numSelected); - selectButton.setTitle(buttonText); - } - }); - } - - getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() { - - @Override - public void onItemClick(final AdapterView parent, final View view, - final int position, final long id) { - final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(getSearchEditText().getWindowToken(), - InputMethodManager.HIDE_IMPLICIT_ONLY); - final Intent request = getIntent(); - final Intent data = new Intent(); - final ListItem mListItem = getListItems().get(position); - data.putExtra("contact", mListItem.getJid().toString()); - String account = request.getStringExtra(EXTRA_ACCOUNT); - if (account == null && mListItem instanceof Contact) { - account = ((Contact) mListItem).getAccount().getJid().toBareJid().toString(); - } - data.putExtra(EXTRA_ACCOUNT, account); - data.putExtra("conversation", - request.getStringExtra("conversation")); - data.putExtra("multiple", false); - data.putExtra("subject", request.getStringExtra("subject")); - setResult(RESULT_OK, data); - finish(); - } - }); - - } - - @Override - public void onStart() { - super.onStart(); - Intent intent = getIntent(); - @StringRes - int res = intent != null ? intent.getIntExtra(EXTRA_TITLE_RES_ID,R.string.title_activity_choose_contact) : R.string.title_activity_choose_contact; - ActionBar bar = getActionBar(); - if (bar != null) { - try { - bar.setTitle(res); - } catch (Exception e) { - bar.setTitle(R.string.title_activity_choose_contact); - } - } - } - - @Override - public boolean onCreateOptionsMenu(final Menu menu) { - super.onCreateOptionsMenu(menu); - final Intent i = getIntent(); - boolean showEnterJid = i != null && i.getBooleanExtra("show_enter_jid", false); - menu.findItem(R.id.action_create_contact).setVisible(showEnterJid); - return true; - } - - protected void filterContacts(final String needle) { - getListItems().clear(); - for (final Account account : xmppConnectionService.getAccounts()) { - if (account.getStatus() != Account.State.DISABLED) { - for (final Contact contact : account.getRoster().getContacts()) { - if (contact.showInRoster() && - !filterContacts.contains(contact.getJid().toBareJid().toString()) - && contact.match(this, needle)) { - getListItems().add(contact); - } - } - } - } - Collections.sort(getListItems()); - getListItemAdapter().notifyDataSetChanged(); - } - - private String[] getSelectedContactJids() { - List result = new ArrayList<>(); - for (Contact contact : selected) { - result.add(contact.getJid().toString()); - } - return result.toArray(new String[result.size()]); - } - - - public void refreshUiReal() { - //nothing to do. This Activity doesn't implement any listeners - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.action_create_contact: - showEnterJidDialog(); - return true; - } - return super.onOptionsItemSelected(item); - } - - protected void showEnterJidDialog() { - EnterJidDialog dialog = new EnterJidDialog( - this, mKnownHosts, mActivatedAccounts, - getString(R.string.enter_contact), getString(R.string.select), - null, getIntent().getStringExtra(EXTRA_ACCOUNT), true - ); - - dialog.setOnEnterJidDialogPositiveListener(new EnterJidDialog.OnEnterJidDialogPositiveListener() { - @Override - public boolean onEnterJidDialogPositive(Jid accountJid, Jid contactJid) throws EnterJidDialog.JidError { - final Intent request = getIntent(); - final Intent data = new Intent(); - data.putExtra("contact", contactJid.toString()); - data.putExtra(EXTRA_ACCOUNT, accountJid.toString()); - data.putExtra("conversation", - request.getStringExtra("conversation")); - data.putExtra("multiple", false); - data.putExtra("subject", request.getStringExtra("subject")); - setResult(RESULT_OK, data); - finish(); - - return true; - } - }); - - dialog.show(); - } - - @Override - void onBackendConnected() { - filterContacts(); - - this.mActivatedAccounts.clear(); - for (Account account : xmppConnectionService.getAccounts()) { - if (account.getStatus() != Account.State.DISABLED) { - if (Config.DOMAIN_LOCK != null) { - this.mActivatedAccounts.add(account.getJid().getLocalpart()); - } else { - this.mActivatedAccounts.add(account.getJid().toBareJid().toString()); - } - } - } - this.mKnownHosts = xmppConnectionService.getKnownHosts(); - } + private List mActivatedAccounts = new ArrayList(); + private List mKnownHosts; + + private Set selected; + private Set filterContacts; + public static final String EXTRA_TITLE_RES_ID = "extra_title_res_id"; + + @Override + public void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + filterContacts = new HashSet<>(); + String[] contacts = getIntent().getStringArrayExtra("filter_contacts"); + if (contacts != null) { + Collections.addAll(filterContacts, contacts); + } + + if (getIntent().getBooleanExtra("multiple", false)) { + getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); + getListView().setMultiChoiceModeListener(new MultiChoiceModeListener() { + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(getSearchEditText().getWindowToken(), + InputMethodManager.HIDE_IMPLICIT_ONLY); + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.select_multiple, menu); + selected = new HashSet(); + return true; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + switch (item.getItemId()) { + case R.id.selection_submit: + final Intent request = getIntent(); + final Intent data = new Intent(); + data.putExtra("conversation", + request.getStringExtra("conversation")); + String[] selection = getSelectedContactJids(); + data.putExtra("contacts", selection); + data.putExtra("multiple", true); + data.putExtra(EXTRA_ACCOUNT, request.getStringExtra(EXTRA_ACCOUNT)); + data.putExtra("subject", request.getStringExtra("subject")); + setResult(RESULT_OK, data); + finish(); + return true; + } + return false; + } + + @Override + public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { + Contact item = (Contact) getListItems().get(position); + if (checked) { + selected.add(item); + } else { + selected.remove(item); + } + int numSelected = selected.size(); + MenuItem selectButton = mode.getMenu().findItem(R.id.selection_submit); + String buttonText = getResources().getQuantityString(R.plurals.select_contact, + numSelected, numSelected); + selectButton.setTitle(buttonText); + } + }); + } + + getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() { + + @Override + public void onItemClick(final AdapterView parent, final View view, + final int position, final long id) { + final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(getSearchEditText().getWindowToken(), + InputMethodManager.HIDE_IMPLICIT_ONLY); + final Intent request = getIntent(); + final Intent data = new Intent(); + final ListItem mListItem = getListItems().get(position); + data.putExtra("contact", mListItem.getJid().toString()); + String account = request.getStringExtra(EXTRA_ACCOUNT); + if (account == null && mListItem instanceof Contact) { + account = ((Contact) mListItem).getAccount().getJid().toBareJid().toString(); + } + data.putExtra(EXTRA_ACCOUNT, account); + data.putExtra("conversation", + request.getStringExtra("conversation")); + data.putExtra("multiple", false); + data.putExtra("subject", request.getStringExtra("subject")); + setResult(RESULT_OK, data); + finish(); + } + }); + + } + + @Override + public void onStart() { + super.onStart(); + Intent intent = getIntent(); + @StringRes + int res = intent != null ? intent.getIntExtra(EXTRA_TITLE_RES_ID, R.string.title_activity_choose_contact) : R.string.title_activity_choose_contact; + ActionBar bar = getActionBar(); + if (bar != null) { + try { + bar.setTitle(res); + } catch (Exception e) { + bar.setTitle(R.string.title_activity_choose_contact); + } + } + } + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + super.onCreateOptionsMenu(menu); + final Intent i = getIntent(); + boolean showEnterJid = i != null && i.getBooleanExtra("show_enter_jid", false); + menu.findItem(R.id.action_create_contact).setVisible(showEnterJid); + return true; + } + + protected void filterContacts(final String needle) { + getListItems().clear(); + for (final Account account : xmppConnectionService.getAccounts()) { + if (account.getStatus() != Account.State.DISABLED) { + for (final Contact contact : account.getRoster().getContacts()) { + if (contact.showInRoster() && + !filterContacts.contains(contact.getJid().toBareJid().toString()) + && contact.match(this, needle)) { + getListItems().add(contact); + } + } + } + } + Collections.sort(getListItems()); + getListItemAdapter().notifyDataSetChanged(); + } + + private String[] getSelectedContactJids() { + List result = new ArrayList<>(); + for (Contact contact : selected) { + result.add(contact.getJid().toString()); + } + return result.toArray(new String[result.size()]); + } + + + public void refreshUiReal() { + //nothing to do. This Activity doesn't implement any listeners + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_create_contact: + showEnterJidDialog(); + return true; + } + return super.onOptionsItemSelected(item); + } + + protected void showEnterJidDialog() { + EnterJidDialog dialog = new EnterJidDialog( + this, mKnownHosts, mActivatedAccounts, + getString(R.string.enter_contact), getString(R.string.select), + null, getIntent().getStringExtra(EXTRA_ACCOUNT), true + ); + + dialog.setOnEnterJidDialogPositiveListener(new EnterJidDialog.OnEnterJidDialogPositiveListener() { + @Override + public boolean onEnterJidDialogPositive(Jid accountJid, Jid contactJid) throws EnterJidDialog.JidError { + final Intent request = getIntent(); + final Intent data = new Intent(); + data.putExtra("contact", contactJid.toString()); + data.putExtra(EXTRA_ACCOUNT, accountJid.toString()); + data.putExtra("conversation", + request.getStringExtra("conversation")); + data.putExtra("multiple", false); + data.putExtra("subject", request.getStringExtra("subject")); + setResult(RESULT_OK, data); + finish(); + + return true; + } + }); + + dialog.show(); + } + + @Override + void onBackendConnected() { + filterContacts(); + + this.mActivatedAccounts.clear(); + for (Account account : xmppConnectionService.getAccounts()) { + if (account.getStatus() != Account.State.DISABLED) { + if (Config.DOMAIN_LOCK != null) { + this.mActivatedAccounts.add(account.getJid().getLocalpart()); + } else { + this.mActivatedAccounts.add(account.getJid().toBareJid().toString()); + } + } + } + this.mKnownHosts = xmppConnectionService.getKnownHosts(); + } } diff --git a/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java b/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java index d91c6e192..672dec4a1 100644 --- a/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java @@ -41,232 +41,232 @@ import de.pixart.messenger.services.XmppConnectionService.OnMucRosterUpdate; import de.pixart.messenger.xmpp.jid.Jid; public class ConferenceDetailsActivity extends XmppActivity implements OnConversationUpdate, OnMucRosterUpdate, XmppConnectionService.OnAffiliationChanged, XmppConnectionService.OnRoleChanged, XmppConnectionService.OnConferenceOptionsPushed { - public static final String ACTION_VIEW_MUC = "view_muc"; - private Conversation mConversation; - private OnClickListener inviteListener = new OnClickListener() { - - @Override - public void onClick(View v) { - inviteToConversation(mConversation); - } - }; - private TextView mYourNick; - private ImageView mYourPhoto; - private ImageButton mEditNickButton; - private TextView mRoleAffiliaton; - private TextView mFullJid; - private TextView mAccountJid; - private LinearLayout membersView; - private LinearLayout mMoreDetails; - private TextView mConferenceType; - private LinearLayout mConferenceInfoTable; - private TextView mConferenceInfoMam; - private TextView mNotifyStatusText; - private ImageButton mChangeConferenceSettingsButton; - private ImageButton mNotifyStatusButton; - private Button mInviteButton; - private String uuid = null; - private User mSelectedUser = null; - - private boolean mAdvancedMode = false; - - private UiCallback renameCallback = new UiCallback() { - @Override - public void success(Conversation object) { - runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(ConferenceDetailsActivity.this,getString(R.string.your_nick_has_been_changed),Toast.LENGTH_SHORT).show(); - updateView(); - } - }); - - } - - @Override - public void error(final int errorCode, Conversation object) { - runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(ConferenceDetailsActivity.this,getString(errorCode),Toast.LENGTH_SHORT).show(); - } - }); - } - - @Override - public void userInputRequried(PendingIntent pi, Conversation object) { - - } - }; - - private OnClickListener mNotifyStatusClickListener = new OnClickListener() { - @Override - public void onClick(View v) { - AlertDialog.Builder builder = new AlertDialog.Builder(ConferenceDetailsActivity.this); - builder.setTitle(R.string.pref_notification_settings); - String[] choices = { - getString(R.string.notify_on_all_messages), - getString(R.string.notify_only_when_highlighted), - getString(R.string.notify_never) - }; - final AtomicInteger choice; - if (mConversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL,0) == Long.MAX_VALUE) { - choice = new AtomicInteger(2); - } else { - choice = new AtomicInteger(mConversation.alwaysNotify() ? 0 : 1); - } - builder.setSingleChoiceItems(choices, choice.get(), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - choice.set(which); - } - }); - builder.setNegativeButton(R.string.cancel, null); - builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (choice.get() == 2) { - mConversation.setMutedTill(Long.MAX_VALUE); - } else { - mConversation.setMutedTill(0); - mConversation.setAttribute(Conversation.ATTRIBUTE_ALWAYS_NOTIFY,String.valueOf(choice.get() == 0)); - } - xmppConnectionService.updateConversation(mConversation); - updateView(); - } - }); - builder.create().show(); - } - }; - - private OnClickListener mChangeConferenceSettings = new OnClickListener() { - @Override - public void onClick(View v) { - final MucOptions mucOptions = mConversation.getMucOptions(); - AlertDialog.Builder builder = new AlertDialog.Builder(ConferenceDetailsActivity.this); - builder.setTitle(R.string.conference_options); - final String[] options; - final boolean[] values; - if (mAdvancedMode) { - options = new String[]{ - getString(R.string.members_only), - getString(R.string.moderated), - getString(R.string.non_anonymous) - }; - values = new boolean[]{ - mucOptions.membersOnly(), - mucOptions.moderated(), - mucOptions.nonanonymous() - }; - } else { - options = new String[]{ - getString(R.string.members_only), - getString(R.string.non_anonymous) - }; - values = new boolean[]{ - mucOptions.membersOnly(), - mucOptions.nonanonymous() - }; - } - builder.setMultiChoiceItems(options,values,new DialogInterface.OnMultiChoiceClickListener() { - @Override - public void onClick(DialogInterface dialog, int which, boolean isChecked) { - values[which] = isChecked; - } - }); - builder.setNegativeButton(R.string.cancel, null); - builder.setPositiveButton(R.string.confirm,new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (!mucOptions.membersOnly() && values[0]) { - xmppConnectionService.changeAffiliationsInConference(mConversation, - MucOptions.Affiliation.NONE, - MucOptions.Affiliation.MEMBER); - } - Bundle options = new Bundle(); - options.putString("muc#roomconfig_membersonly", values[0] ? "1" : "0"); - if (values.length == 2) { - options.putString("muc#roomconfig_whois", values[1] ? "anyone" : "moderators"); - } else if (values.length == 3) { - options.putString("muc#roomconfig_moderatedroom", values[1] ? "1" : "0"); - options.putString("muc#roomconfig_whois", values[2] ? "anyone" : "moderators"); - } - options.putString("muc#roomconfig_persistentroom", "1"); - xmppConnectionService.pushConferenceConfiguration(mConversation, - options, - ConferenceDetailsActivity.this); - } - }); - builder.create().show(); - } - }; - private OnValueEdited onSubjectEdited = new OnValueEdited() { - - @Override - public void onValueEdited(String value) { - xmppConnectionService.pushSubjectToConference(mConversation,value); - } - }; - - @Override - public void onConversationUpdate() { - refreshUi(); - } - - @Override - public void onMucRosterUpdate() { - refreshUi(); - } - - @Override - protected void refreshUiReal() { - updateView(); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_muc_details); - mYourNick = (TextView) findViewById(R.id.muc_your_nick); - mYourPhoto = (ImageView) findViewById(R.id.your_photo); - mEditNickButton = (ImageButton) findViewById(R.id.edit_nick_button); - mFullJid = (TextView) findViewById(R.id.muc_jabberid); - membersView = (LinearLayout) findViewById(R.id.muc_members); - mAccountJid = (TextView) findViewById(R.id.details_account); - mMoreDetails = (LinearLayout) findViewById(R.id.muc_more_details); - mMoreDetails.setVisibility(View.GONE); - mChangeConferenceSettingsButton = (ImageButton) findViewById(R.id.change_conference_button); - mChangeConferenceSettingsButton.setOnClickListener(this.mChangeConferenceSettings); - mInviteButton = (Button) findViewById(R.id.invite); - mInviteButton.setOnClickListener(inviteListener); - mConferenceType = (TextView) findViewById(R.id.muc_conference_type); - if (getActionBar() != null) { - getActionBar().setHomeButtonEnabled(true); - getActionBar().setDisplayHomeAsUpEnabled(true); - } - mEditNickButton.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - quickEdit(mConversation.getMucOptions().getActualNick(), - 0, - new OnValueEdited() { - - @Override - public void onValueEdited(String value) { - xmppConnectionService.renameInMuc(mConversation,value,renameCallback); - } - }); - } - }); - this.mAdvancedMode = getPreferences().getBoolean("advanced_muc_mode", false); - this.mConferenceInfoTable = (LinearLayout) findViewById(R.id.muc_info_more); - mConferenceInfoTable.setVisibility(this.mAdvancedMode ? View.VISIBLE : View.GONE); - this.mConferenceInfoMam = (TextView) findViewById(R.id.muc_info_mam); - this.mNotifyStatusButton = (ImageButton) findViewById(R.id.notification_status_button); - this.mNotifyStatusButton.setOnClickListener(this.mNotifyStatusClickListener); - this.mNotifyStatusText = (TextView) findViewById(R.id.notification_status_text); - } + public static final String ACTION_VIEW_MUC = "view_muc"; + private Conversation mConversation; + private OnClickListener inviteListener = new OnClickListener() { + + @Override + public void onClick(View v) { + inviteToConversation(mConversation); + } + }; + private TextView mYourNick; + private ImageView mYourPhoto; + private ImageButton mEditNickButton; + private TextView mRoleAffiliaton; + private TextView mFullJid; + private TextView mAccountJid; + private LinearLayout membersView; + private LinearLayout mMoreDetails; + private TextView mConferenceType; + private LinearLayout mConferenceInfoTable; + private TextView mConferenceInfoMam; + private TextView mNotifyStatusText; + private ImageButton mChangeConferenceSettingsButton; + private ImageButton mNotifyStatusButton; + private Button mInviteButton; + private String uuid = null; + private User mSelectedUser = null; + + private boolean mAdvancedMode = false; + + private UiCallback renameCallback = new UiCallback() { + @Override + public void success(Conversation object) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(ConferenceDetailsActivity.this, getString(R.string.your_nick_has_been_changed), Toast.LENGTH_SHORT).show(); + updateView(); + } + }); + + } + + @Override + public void error(final int errorCode, Conversation object) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(ConferenceDetailsActivity.this, getString(errorCode), Toast.LENGTH_SHORT).show(); + } + }); + } + + @Override + public void userInputRequried(PendingIntent pi, Conversation object) { + + } + }; + + private OnClickListener mNotifyStatusClickListener = new OnClickListener() { + @Override + public void onClick(View v) { + AlertDialog.Builder builder = new AlertDialog.Builder(ConferenceDetailsActivity.this); + builder.setTitle(R.string.pref_notification_settings); + String[] choices = { + getString(R.string.notify_on_all_messages), + getString(R.string.notify_only_when_highlighted), + getString(R.string.notify_never) + }; + final AtomicInteger choice; + if (mConversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL, 0) == Long.MAX_VALUE) { + choice = new AtomicInteger(2); + } else { + choice = new AtomicInteger(mConversation.alwaysNotify() ? 0 : 1); + } + builder.setSingleChoiceItems(choices, choice.get(), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + choice.set(which); + } + }); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (choice.get() == 2) { + mConversation.setMutedTill(Long.MAX_VALUE); + } else { + mConversation.setMutedTill(0); + mConversation.setAttribute(Conversation.ATTRIBUTE_ALWAYS_NOTIFY, String.valueOf(choice.get() == 0)); + } + xmppConnectionService.updateConversation(mConversation); + updateView(); + } + }); + builder.create().show(); + } + }; + + private OnClickListener mChangeConferenceSettings = new OnClickListener() { + @Override + public void onClick(View v) { + final MucOptions mucOptions = mConversation.getMucOptions(); + AlertDialog.Builder builder = new AlertDialog.Builder(ConferenceDetailsActivity.this); + builder.setTitle(R.string.conference_options); + final String[] options; + final boolean[] values; + if (mAdvancedMode) { + options = new String[]{ + getString(R.string.members_only), + getString(R.string.moderated), + getString(R.string.non_anonymous) + }; + values = new boolean[]{ + mucOptions.membersOnly(), + mucOptions.moderated(), + mucOptions.nonanonymous() + }; + } else { + options = new String[]{ + getString(R.string.members_only), + getString(R.string.non_anonymous) + }; + values = new boolean[]{ + mucOptions.membersOnly(), + mucOptions.nonanonymous() + }; + } + builder.setMultiChoiceItems(options, values, new DialogInterface.OnMultiChoiceClickListener() { + @Override + public void onClick(DialogInterface dialog, int which, boolean isChecked) { + values[which] = isChecked; + } + }); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(R.string.confirm, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (!mucOptions.membersOnly() && values[0]) { + xmppConnectionService.changeAffiliationsInConference(mConversation, + MucOptions.Affiliation.NONE, + MucOptions.Affiliation.MEMBER); + } + Bundle options = new Bundle(); + options.putString("muc#roomconfig_membersonly", values[0] ? "1" : "0"); + if (values.length == 2) { + options.putString("muc#roomconfig_whois", values[1] ? "anyone" : "moderators"); + } else if (values.length == 3) { + options.putString("muc#roomconfig_moderatedroom", values[1] ? "1" : "0"); + options.putString("muc#roomconfig_whois", values[2] ? "anyone" : "moderators"); + } + options.putString("muc#roomconfig_persistentroom", "1"); + xmppConnectionService.pushConferenceConfiguration(mConversation, + options, + ConferenceDetailsActivity.this); + } + }); + builder.create().show(); + } + }; + private OnValueEdited onSubjectEdited = new OnValueEdited() { + + @Override + public void onValueEdited(String value) { + xmppConnectionService.pushSubjectToConference(mConversation, value); + } + }; + + @Override + public void onConversationUpdate() { + refreshUi(); + } + + @Override + public void onMucRosterUpdate() { + refreshUi(); + } + + @Override + protected void refreshUiReal() { + updateView(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_muc_details); + mYourNick = (TextView) findViewById(R.id.muc_your_nick); + mYourPhoto = (ImageView) findViewById(R.id.your_photo); + mEditNickButton = (ImageButton) findViewById(R.id.edit_nick_button); + mFullJid = (TextView) findViewById(R.id.muc_jabberid); + membersView = (LinearLayout) findViewById(R.id.muc_members); + mAccountJid = (TextView) findViewById(R.id.details_account); + mMoreDetails = (LinearLayout) findViewById(R.id.muc_more_details); + mMoreDetails.setVisibility(View.GONE); + mChangeConferenceSettingsButton = (ImageButton) findViewById(R.id.change_conference_button); + mChangeConferenceSettingsButton.setOnClickListener(this.mChangeConferenceSettings); + mInviteButton = (Button) findViewById(R.id.invite); + mInviteButton.setOnClickListener(inviteListener); + mConferenceType = (TextView) findViewById(R.id.muc_conference_type); + if (getActionBar() != null) { + getActionBar().setHomeButtonEnabled(true); + getActionBar().setDisplayHomeAsUpEnabled(true); + } + mEditNickButton.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + quickEdit(mConversation.getMucOptions().getActualNick(), + 0, + new OnValueEdited() { + + @Override + public void onValueEdited(String value) { + xmppConnectionService.renameInMuc(mConversation, value, renameCallback); + } + }); + } + }); + this.mAdvancedMode = getPreferences().getBoolean("advanced_muc_mode", false); + this.mConferenceInfoTable = (LinearLayout) findViewById(R.id.muc_info_more); + mConferenceInfoTable.setVisibility(this.mAdvancedMode ? View.VISIBLE : View.GONE); + this.mConferenceInfoMam = (TextView) findViewById(R.id.muc_info_mam); + this.mNotifyStatusButton = (ImageButton) findViewById(R.id.notification_status_button); + this.mNotifyStatusButton.setOnClickListener(this.mNotifyStatusClickListener); + this.mNotifyStatusText = (TextView) findViewById(R.id.notification_status_text); + } @Override protected void onStart() { @@ -277,245 +277,245 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers } } - @Override - public boolean onOptionsItemSelected(MenuItem menuItem) { - switch (menuItem.getItemId()) { - case android.R.id.home: - finish(); - break; - case R.id.action_edit_subject: - if (mConversation != null) { - quickEdit(mConversation.getMucOptions().getSubject(), - R.string.edit_subject_hint, - this.onSubjectEdited); - } - break; - case R.id.action_share: - shareUri(); - break; - case R.id.action_save_as_bookmark: - saveAsBookmark(); - break; - case R.id.action_delete_bookmark: - deleteBookmark(); - break; - case R.id.action_advanced_mode: - this.mAdvancedMode = !menuItem.isChecked(); - menuItem.setChecked(this.mAdvancedMode); - getPreferences().edit().putBoolean("advanced_muc_mode", mAdvancedMode).commit(); - mConferenceInfoTable.setVisibility(this.mAdvancedMode ? View.VISIBLE : View.GONE); - invalidateOptionsMenu(); - updateView(); - break; - } - return super.onOptionsItemSelected(menuItem); - } - - @Override - protected String getShareableUri() { - if (mConversation != null) { - return "xmpp:" + mConversation.getJid().toBareJid().toString() + "?join"; - } else { - return ""; - } - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - MenuItem menuItemSaveBookmark = menu.findItem(R.id.action_save_as_bookmark); - MenuItem menuItemDeleteBookmark = menu.findItem(R.id.action_delete_bookmark); - MenuItem menuItemAdvancedMode = menu.findItem(R.id.action_advanced_mode); - MenuItem menuItemChangeSubject = menu.findItem(R.id.action_edit_subject); - menuItemAdvancedMode.setChecked(mAdvancedMode); - if (mConversation == null) { - return true; - } - Account account = mConversation.getAccount(); - if (account.hasBookmarkFor(mConversation.getJid().toBareJid())) { - menuItemSaveBookmark.setVisible(false); - menuItemDeleteBookmark.setVisible(true); - } else { - menuItemDeleteBookmark.setVisible(false); - menuItemSaveBookmark.setVisible(true); - } - menuItemChangeSubject.setVisible(mConversation.getMucOptions().canChangeSubject()); - return true; - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.muc_details, menu); - return super.onCreateOptionsMenu(menu); - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { - Object tag = v.getTag(); - if (tag instanceof User) { - getMenuInflater().inflate(R.menu.muc_details_context,menu); - final User user = (User) tag; - final User self = mConversation.getMucOptions().getSelf(); - this.mSelectedUser = user; - String name; - final Contact contact = user.getContact(); - if (contact != null) { - name = contact.getDisplayName(); - } else if (user.getRealJid() != null){ - name = user.getRealJid().toBareJid().toString(); - } else { - name = user.getName(); - } - menu.setHeaderTitle(name); - if (user.getRealJid() != null) { - MenuItem startConversation = menu.findItem(R.id.start_conversation); - MenuItem giveMembership = menu.findItem(R.id.give_membership); - MenuItem removeMembership = menu.findItem(R.id.remove_membership); - MenuItem giveAdminPrivileges = menu.findItem(R.id.give_admin_privileges); - MenuItem removeAdminPrivileges = menu.findItem(R.id.remove_admin_privileges); - MenuItem removeFromRoom = menu.findItem(R.id.remove_from_room); - MenuItem banFromConference = menu.findItem(R.id.ban_from_conference); - MenuItem invite = menu.findItem(R.id.invite); - startConversation.setVisible(true); - if (user.getRole() == MucOptions.Role.NONE) { - invite.setVisible(true); - } - if (self.getAffiliation().ranks(MucOptions.Affiliation.ADMIN) && - self.getAffiliation().outranks(user.getAffiliation())) { - if (mAdvancedMode) { - if (user.getAffiliation() == MucOptions.Affiliation.NONE) { - giveMembership.setVisible(true); - } else { - removeMembership.setVisible(true); - } - banFromConference.setVisible(true); - } else { - removeFromRoom.setVisible(true); - } - if (user.getAffiliation() != MucOptions.Affiliation.ADMIN) { - giveAdminPrivileges.setVisible(true); - } else { - removeAdminPrivileges.setVisible(true); - } - } - } else { - MenuItem sendPrivateMessage = menu.findItem(R.id.send_private_message); - sendPrivateMessage.setVisible(user.getRole().ranks(MucOptions.Role.PARTICIPANT)); - } - - } - super.onCreateContextMenu(menu, v, menuInfo); - } - - @Override - public boolean onContextItemSelected(MenuItem item) { - Jid jid = mSelectedUser.getRealJid(); - switch (item.getItemId()) { - case R.id.start_conversation: - startConversation(mSelectedUser); - return true; - case R.id.give_admin_privileges: - xmppConnectionService.changeAffiliationInConference(mConversation, jid, MucOptions.Affiliation.ADMIN,this); - return true; - case R.id.give_membership: - xmppConnectionService.changeAffiliationInConference(mConversation, jid, MucOptions.Affiliation.MEMBER,this); - return true; - case R.id.remove_membership: - xmppConnectionService.changeAffiliationInConference(mConversation, jid, MucOptions.Affiliation.NONE,this); - return true; - case R.id.remove_admin_privileges: - xmppConnectionService.changeAffiliationInConference(mConversation, jid, MucOptions.Affiliation.MEMBER,this); - return true; - case R.id.remove_from_room: - removeFromRoom(mSelectedUser); - return true; - case R.id.ban_from_conference: - xmppConnectionService.changeAffiliationInConference(mConversation,jid, MucOptions.Affiliation.OUTCAST,this); - if (mSelectedUser.getRole() != MucOptions.Role.NONE) { - xmppConnectionService.changeRoleInConference(mConversation, mSelectedUser.getName(), MucOptions.Role.NONE, this); - } - return true; - case R.id.send_private_message: - privateMsgInMuc(mConversation,mSelectedUser.getName()); - return true; - case R.id.invite: - xmppConnectionService.directInvite(mConversation, jid); - return true; - default: - return super.onContextItemSelected(item); - } - } - - private void removeFromRoom(final User user) { - if (mConversation.getMucOptions().membersOnly()) { - xmppConnectionService.changeAffiliationInConference(mConversation,user.getRealJid(), MucOptions.Affiliation.NONE,this); - if (user.getRole() != MucOptions.Role.NONE) { - xmppConnectionService.changeRoleInConference(mConversation, mSelectedUser.getName(), MucOptions.Role.NONE, ConferenceDetailsActivity.this); - } - } else { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.ban_from_conference); - builder.setMessage(getString(R.string.removing_from_public_conference,user.getName())); - builder.setNegativeButton(R.string.cancel,null); - builder.setPositiveButton(R.string.ban_now,new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - xmppConnectionService.changeAffiliationInConference(mConversation,user.getRealJid(), MucOptions.Affiliation.OUTCAST,ConferenceDetailsActivity.this); - if (user.getRole() != MucOptions.Role.NONE) { - xmppConnectionService.changeRoleInConference(mConversation, mSelectedUser.getName(), MucOptions.Role.NONE, ConferenceDetailsActivity.this); - } - } - }); - builder.create().show(); - } - } - - protected void startConversation(User user) { - if (user.getRealJid() != null) { - Conversation conversation = xmppConnectionService.findOrCreateConversation(this.mConversation.getAccount(),user.getRealJid().toBareJid(),false); - switchToConversation(conversation); - } - } - - protected void saveAsBookmark() { - xmppConnectionService.saveConversationAsBookmark(mConversation, - mConversation.getMucOptions().getSubject()); - } - - protected void deleteBookmark() { - Account account = mConversation.getAccount(); - Bookmark bookmark = mConversation.getBookmark(); - bookmark.unregisterConversation(); - account.getBookmarks().remove(bookmark); - xmppConnectionService.pushBookmarks(account); - } - - @Override - void onBackendConnected() { - if (mPendingConferenceInvite != null) { - mPendingConferenceInvite.execute(this); - mPendingConferenceInvite = null; - } - if (getIntent().getAction().equals(ACTION_VIEW_MUC)) { - this.uuid = getIntent().getExtras().getString("uuid"); - } - if (uuid != null) { - this.mConversation = xmppConnectionService - .findConversationByUuid(uuid); - if (this.mConversation != null) { - updateView(); - } - } - } - - private void updateView() { - final MucOptions mucOptions = mConversation.getMucOptions(); - final User self = mucOptions.getSelf(); - String account; - if (Config.DOMAIN_LOCK != null) { - account = mConversation.getAccount().getJid().getLocalpart(); - } else { - account = mConversation.getAccount().getJid().toBareJid().toString(); - } + @Override + public boolean onOptionsItemSelected(MenuItem menuItem) { + switch (menuItem.getItemId()) { + case android.R.id.home: + finish(); + break; + case R.id.action_edit_subject: + if (mConversation != null) { + quickEdit(mConversation.getMucOptions().getSubject(), + R.string.edit_subject_hint, + this.onSubjectEdited); + } + break; + case R.id.action_share: + shareUri(); + break; + case R.id.action_save_as_bookmark: + saveAsBookmark(); + break; + case R.id.action_delete_bookmark: + deleteBookmark(); + break; + case R.id.action_advanced_mode: + this.mAdvancedMode = !menuItem.isChecked(); + menuItem.setChecked(this.mAdvancedMode); + getPreferences().edit().putBoolean("advanced_muc_mode", mAdvancedMode).commit(); + mConferenceInfoTable.setVisibility(this.mAdvancedMode ? View.VISIBLE : View.GONE); + invalidateOptionsMenu(); + updateView(); + break; + } + return super.onOptionsItemSelected(menuItem); + } + + @Override + protected String getShareableUri() { + if (mConversation != null) { + return "xmpp:" + mConversation.getJid().toBareJid().toString() + "?join"; + } else { + return ""; + } + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + MenuItem menuItemSaveBookmark = menu.findItem(R.id.action_save_as_bookmark); + MenuItem menuItemDeleteBookmark = menu.findItem(R.id.action_delete_bookmark); + MenuItem menuItemAdvancedMode = menu.findItem(R.id.action_advanced_mode); + MenuItem menuItemChangeSubject = menu.findItem(R.id.action_edit_subject); + menuItemAdvancedMode.setChecked(mAdvancedMode); + if (mConversation == null) { + return true; + } + Account account = mConversation.getAccount(); + if (account.hasBookmarkFor(mConversation.getJid().toBareJid())) { + menuItemSaveBookmark.setVisible(false); + menuItemDeleteBookmark.setVisible(true); + } else { + menuItemDeleteBookmark.setVisible(false); + menuItemSaveBookmark.setVisible(true); + } + menuItemChangeSubject.setVisible(mConversation.getMucOptions().canChangeSubject()); + return true; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.muc_details, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + Object tag = v.getTag(); + if (tag instanceof User) { + getMenuInflater().inflate(R.menu.muc_details_context, menu); + final User user = (User) tag; + final User self = mConversation.getMucOptions().getSelf(); + this.mSelectedUser = user; + String name; + final Contact contact = user.getContact(); + if (contact != null) { + name = contact.getDisplayName(); + } else if (user.getRealJid() != null) { + name = user.getRealJid().toBareJid().toString(); + } else { + name = user.getName(); + } + menu.setHeaderTitle(name); + if (user.getRealJid() != null) { + MenuItem startConversation = menu.findItem(R.id.start_conversation); + MenuItem giveMembership = menu.findItem(R.id.give_membership); + MenuItem removeMembership = menu.findItem(R.id.remove_membership); + MenuItem giveAdminPrivileges = menu.findItem(R.id.give_admin_privileges); + MenuItem removeAdminPrivileges = menu.findItem(R.id.remove_admin_privileges); + MenuItem removeFromRoom = menu.findItem(R.id.remove_from_room); + MenuItem banFromConference = menu.findItem(R.id.ban_from_conference); + MenuItem invite = menu.findItem(R.id.invite); + startConversation.setVisible(true); + if (user.getRole() == MucOptions.Role.NONE) { + invite.setVisible(true); + } + if (self.getAffiliation().ranks(MucOptions.Affiliation.ADMIN) && + self.getAffiliation().outranks(user.getAffiliation())) { + if (mAdvancedMode) { + if (user.getAffiliation() == MucOptions.Affiliation.NONE) { + giveMembership.setVisible(true); + } else { + removeMembership.setVisible(true); + } + banFromConference.setVisible(true); + } else { + removeFromRoom.setVisible(true); + } + if (user.getAffiliation() != MucOptions.Affiliation.ADMIN) { + giveAdminPrivileges.setVisible(true); + } else { + removeAdminPrivileges.setVisible(true); + } + } + } else { + MenuItem sendPrivateMessage = menu.findItem(R.id.send_private_message); + sendPrivateMessage.setVisible(user.getRole().ranks(MucOptions.Role.PARTICIPANT)); + } + + } + super.onCreateContextMenu(menu, v, menuInfo); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + Jid jid = mSelectedUser.getRealJid(); + switch (item.getItemId()) { + case R.id.start_conversation: + startConversation(mSelectedUser); + return true; + case R.id.give_admin_privileges: + xmppConnectionService.changeAffiliationInConference(mConversation, jid, MucOptions.Affiliation.ADMIN, this); + return true; + case R.id.give_membership: + xmppConnectionService.changeAffiliationInConference(mConversation, jid, MucOptions.Affiliation.MEMBER, this); + return true; + case R.id.remove_membership: + xmppConnectionService.changeAffiliationInConference(mConversation, jid, MucOptions.Affiliation.NONE, this); + return true; + case R.id.remove_admin_privileges: + xmppConnectionService.changeAffiliationInConference(mConversation, jid, MucOptions.Affiliation.MEMBER, this); + return true; + case R.id.remove_from_room: + removeFromRoom(mSelectedUser); + return true; + case R.id.ban_from_conference: + xmppConnectionService.changeAffiliationInConference(mConversation, jid, MucOptions.Affiliation.OUTCAST, this); + if (mSelectedUser.getRole() != MucOptions.Role.NONE) { + xmppConnectionService.changeRoleInConference(mConversation, mSelectedUser.getName(), MucOptions.Role.NONE, this); + } + return true; + case R.id.send_private_message: + privateMsgInMuc(mConversation, mSelectedUser.getName()); + return true; + case R.id.invite: + xmppConnectionService.directInvite(mConversation, jid); + return true; + default: + return super.onContextItemSelected(item); + } + } + + private void removeFromRoom(final User user) { + if (mConversation.getMucOptions().membersOnly()) { + xmppConnectionService.changeAffiliationInConference(mConversation, user.getRealJid(), MucOptions.Affiliation.NONE, this); + if (user.getRole() != MucOptions.Role.NONE) { + xmppConnectionService.changeRoleInConference(mConversation, mSelectedUser.getName(), MucOptions.Role.NONE, ConferenceDetailsActivity.this); + } + } else { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.ban_from_conference); + builder.setMessage(getString(R.string.removing_from_public_conference, user.getName())); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(R.string.ban_now, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + xmppConnectionService.changeAffiliationInConference(mConversation, user.getRealJid(), MucOptions.Affiliation.OUTCAST, ConferenceDetailsActivity.this); + if (user.getRole() != MucOptions.Role.NONE) { + xmppConnectionService.changeRoleInConference(mConversation, mSelectedUser.getName(), MucOptions.Role.NONE, ConferenceDetailsActivity.this); + } + } + }); + builder.create().show(); + } + } + + protected void startConversation(User user) { + if (user.getRealJid() != null) { + Conversation conversation = xmppConnectionService.findOrCreateConversation(this.mConversation.getAccount(), user.getRealJid().toBareJid(), false); + switchToConversation(conversation); + } + } + + protected void saveAsBookmark() { + xmppConnectionService.saveConversationAsBookmark(mConversation, + mConversation.getMucOptions().getSubject()); + } + + protected void deleteBookmark() { + Account account = mConversation.getAccount(); + Bookmark bookmark = mConversation.getBookmark(); + bookmark.unregisterConversation(); + account.getBookmarks().remove(bookmark); + xmppConnectionService.pushBookmarks(account); + } + + @Override + void onBackendConnected() { + if (mPendingConferenceInvite != null) { + mPendingConferenceInvite.execute(this); + mPendingConferenceInvite = null; + } + if (getIntent().getAction().equals(ACTION_VIEW_MUC)) { + this.uuid = getIntent().getExtras().getString("uuid"); + } + if (uuid != null) { + this.mConversation = xmppConnectionService + .findConversationByUuid(uuid); + if (this.mConversation != null) { + updateView(); + } + } + } + + private void updateView() { + final MucOptions mucOptions = mConversation.getMucOptions(); + final User self = mucOptions.getSelf(); + String account; + if (Config.DOMAIN_LOCK != null) { + account = mConversation.getAccount().getJid().getLocalpart(); + } else { + account = mConversation.getAccount().getJid().toBareJid().toString(); + } if (getActionBar() != null) { final ActionBar ab = getActionBar(); ab.setCustomView(R.layout.ab_title); @@ -528,61 +528,61 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers absubtitle.setVisibility(View.GONE); absubtitle.setClickable(false); } - mAccountJid.setText(getString(R.string.using_account, account)); - mYourPhoto.setImageBitmap(avatarService().get(mConversation.getAccount(), getPixel(48))); - setTitle(mConversation.getName()); - mFullJid.setText(mConversation.getJid().toBareJid().toString()); - mYourNick.setText(mucOptions.getActualNick()); - mRoleAffiliaton = (TextView) findViewById(R.id.muc_role); - if (mucOptions.online()) { - mMoreDetails.setVisibility(View.VISIBLE); - final String status = getStatus(self); - if (status != null) { - mRoleAffiliaton.setVisibility(View.VISIBLE); - mRoleAffiliaton.setText(status); - } else { - mRoleAffiliaton.setVisibility(View.GONE); - } - if (mucOptions.membersOnly()) { - mConferenceType.setText(R.string.private_conference); - } else { - mConferenceType.setText(R.string.public_conference); - } - if (mucOptions.mamSupport()) { - mConferenceInfoMam.setText(R.string.server_info_available); - } else { - mConferenceInfoMam.setText(R.string.server_info_unavailable); - } - if (self.getAffiliation().ranks(MucOptions.Affiliation.OWNER)) { - mChangeConferenceSettingsButton.setVisibility(View.VISIBLE); - } else { - mChangeConferenceSettingsButton.setVisibility(View.GONE); - } - } - - long mutedTill = mConversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL,0); - if (mutedTill == Long.MAX_VALUE) { - mNotifyStatusText.setText(R.string.notify_never); - mNotifyStatusButton.setImageResource(R.drawable.ic_notifications_off_grey600_24dp); - } else if (System.currentTimeMillis() < mutedTill) { - mNotifyStatusText.setText(R.string.notify_paused); - mNotifyStatusButton.setImageResource(R.drawable.ic_notifications_paused_grey600_24dp); - } else if (mConversation.alwaysNotify()) { - mNotifyStatusButton.setImageResource(R.drawable.ic_notifications_grey600_24dp); - mNotifyStatusText.setText(R.string.notify_on_all_messages); - } else { - mNotifyStatusButton.setImageResource(R.drawable.ic_notifications_none_grey600_24dp); - mNotifyStatusText.setText(R.string.notify_only_when_highlighted); - } - - LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); - membersView.removeAllViews(); - final ArrayList users = mucOptions.getUsers(); + mAccountJid.setText(getString(R.string.using_account, account)); + mYourPhoto.setImageBitmap(avatarService().get(mConversation.getAccount(), getPixel(48))); + setTitle(mConversation.getName()); + mFullJid.setText(mConversation.getJid().toBareJid().toString()); + mYourNick.setText(mucOptions.getActualNick()); + mRoleAffiliaton = (TextView) findViewById(R.id.muc_role); + if (mucOptions.online()) { + mMoreDetails.setVisibility(View.VISIBLE); + final String status = getStatus(self); + if (status != null) { + mRoleAffiliaton.setVisibility(View.VISIBLE); + mRoleAffiliaton.setText(status); + } else { + mRoleAffiliaton.setVisibility(View.GONE); + } + if (mucOptions.membersOnly()) { + mConferenceType.setText(R.string.private_conference); + } else { + mConferenceType.setText(R.string.public_conference); + } + if (mucOptions.mamSupport()) { + mConferenceInfoMam.setText(R.string.server_info_available); + } else { + mConferenceInfoMam.setText(R.string.server_info_unavailable); + } + if (self.getAffiliation().ranks(MucOptions.Affiliation.OWNER)) { + mChangeConferenceSettingsButton.setVisibility(View.VISIBLE); + } else { + mChangeConferenceSettingsButton.setVisibility(View.GONE); + } + } + + long mutedTill = mConversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL, 0); + if (mutedTill == Long.MAX_VALUE) { + mNotifyStatusText.setText(R.string.notify_never); + mNotifyStatusButton.setImageResource(R.drawable.ic_notifications_off_grey600_24dp); + } else if (System.currentTimeMillis() < mutedTill) { + mNotifyStatusText.setText(R.string.notify_paused); + mNotifyStatusButton.setImageResource(R.drawable.ic_notifications_paused_grey600_24dp); + } else if (mConversation.alwaysNotify()) { + mNotifyStatusButton.setImageResource(R.drawable.ic_notifications_grey600_24dp); + mNotifyStatusText.setText(R.string.notify_on_all_messages); + } else { + mNotifyStatusButton.setImageResource(R.drawable.ic_notifications_none_grey600_24dp); + mNotifyStatusText.setText(R.string.notify_only_when_highlighted); + } + + LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); + membersView.removeAllViews(); + final ArrayList users = mucOptions.getUsers(); Collections.sort(users); - for (final User user : users) { + for (final User user : users) { final Contact contact = user.getContact(); - View view = inflater.inflate(R.layout.contact, membersView,false); - this.setListItemBackgroundOnView(view); + View view = inflater.inflate(R.layout.contact, membersView, false); + this.setListItemBackgroundOnView(view); if (contact != null) { view.setOnClickListener(new OnClickListener() { @Override @@ -591,105 +591,105 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers } }); } - registerForContextMenu(view); - view.setTag(user); - TextView tvDisplayName = (TextView) view.findViewById(R.id.contact_display_name); - TextView tvKey = (TextView) view.findViewById(R.id.key); - TextView tvStatus = (TextView) view.findViewById(R.id.contact_jid); - if (mAdvancedMode && user.getPgpKeyId() != 0) { - tvKey.setVisibility(View.VISIBLE); - tvKey.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - viewPgpKey(user); - } - }); - tvKey.setText(OpenPgpUtils.convertKeyIdToHex(user.getPgpKeyId())); - } - String name = user.getName(); - if (contact != null) { - tvDisplayName.setText(contact.getDisplayName()); - tvStatus.setText((name != null ? name+ " \u2022 " : "") + getStatus(user)); - } else { - tvDisplayName.setText(name == null ? "" : name); - tvStatus.setText(getStatus(user)); - - } - ImageView iv = (ImageView) view.findViewById(R.id.contact_photo); - iv.setImageBitmap(avatarService().get(user, getPixel(48), false)); - membersView.addView(view); - if (mConversation.getMucOptions().canInvite()) { - mInviteButton.setVisibility(View.VISIBLE); - } else { - mInviteButton.setVisibility(View.GONE); - } - } - } - - private String getStatus(User user) { - if (mAdvancedMode) { - StringBuilder builder = new StringBuilder(); - builder.append(getString(user.getAffiliation().getResId())); - builder.append(" ("); - builder.append(getString(user.getRole().getResId())); - builder.append(')'); - return builder.toString(); - } else { - return getString(user.getAffiliation().getResId()); - } - } - - private void viewPgpKey(User user) { - PgpEngine pgp = xmppConnectionService.getPgpEngine(); - if (pgp != null) { - PendingIntent intent = pgp.getIntentForKey(user.getPgpKeyId()); - if (intent != null) { - try { - startIntentSenderForResult(intent.getIntentSender(), 0, null, 0, 0, 0); - } catch (SendIntentException ignored) { - - } - } - } - } - - @Override - public void onAffiliationChangedSuccessful(Jid jid) { - refreshUi(); - } - - @Override - public void onAffiliationChangeFailed(Jid jid, int resId) { - displayToast(getString(resId,jid.toBareJid().toString())); - } - - @Override - public void onRoleChangedSuccessful(String nick) { - - } - - @Override - public void onRoleChangeFailed(String nick, int resId) { - displayToast(getString(resId,nick)); - } - - @Override - public void onPushSucceeded() { - displayToast(getString(R.string.modified_conference_options)); - } - - @Override - public void onPushFailed() { - displayToast(getString(R.string.could_not_modify_conference_options)); - } - - private void displayToast(final String msg) { - runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(ConferenceDetailsActivity.this,msg,Toast.LENGTH_SHORT).show(); - } - }); - } + registerForContextMenu(view); + view.setTag(user); + TextView tvDisplayName = (TextView) view.findViewById(R.id.contact_display_name); + TextView tvKey = (TextView) view.findViewById(R.id.key); + TextView tvStatus = (TextView) view.findViewById(R.id.contact_jid); + if (mAdvancedMode && user.getPgpKeyId() != 0) { + tvKey.setVisibility(View.VISIBLE); + tvKey.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + viewPgpKey(user); + } + }); + tvKey.setText(OpenPgpUtils.convertKeyIdToHex(user.getPgpKeyId())); + } + String name = user.getName(); + if (contact != null) { + tvDisplayName.setText(contact.getDisplayName()); + tvStatus.setText((name != null ? name + " \u2022 " : "") + getStatus(user)); + } else { + tvDisplayName.setText(name == null ? "" : name); + tvStatus.setText(getStatus(user)); + + } + ImageView iv = (ImageView) view.findViewById(R.id.contact_photo); + iv.setImageBitmap(avatarService().get(user, getPixel(48), false)); + membersView.addView(view); + if (mConversation.getMucOptions().canInvite()) { + mInviteButton.setVisibility(View.VISIBLE); + } else { + mInviteButton.setVisibility(View.GONE); + } + } + } + + private String getStatus(User user) { + if (mAdvancedMode) { + StringBuilder builder = new StringBuilder(); + builder.append(getString(user.getAffiliation().getResId())); + builder.append(" ("); + builder.append(getString(user.getRole().getResId())); + builder.append(')'); + return builder.toString(); + } else { + return getString(user.getAffiliation().getResId()); + } + } + + private void viewPgpKey(User user) { + PgpEngine pgp = xmppConnectionService.getPgpEngine(); + if (pgp != null) { + PendingIntent intent = pgp.getIntentForKey(user.getPgpKeyId()); + if (intent != null) { + try { + startIntentSenderForResult(intent.getIntentSender(), 0, null, 0, 0, 0); + } catch (SendIntentException ignored) { + + } + } + } + } + + @Override + public void onAffiliationChangedSuccessful(Jid jid) { + refreshUi(); + } + + @Override + public void onAffiliationChangeFailed(Jid jid, int resId) { + displayToast(getString(resId, jid.toBareJid().toString())); + } + + @Override + public void onRoleChangedSuccessful(String nick) { + + } + + @Override + public void onRoleChangeFailed(String nick, int resId) { + displayToast(getString(resId, nick)); + } + + @Override + public void onPushSucceeded() { + displayToast(getString(R.string.modified_conference_options)); + } + + @Override + public void onPushFailed() { + displayToast(getString(R.string.could_not_modify_conference_options)); + } + + private void displayToast(final String msg) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(ConferenceDetailsActivity.this, msg, Toast.LENGTH_SHORT).show(); + } + }); + } } diff --git a/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java b/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java index 51b9b33ec..31d131549 100644 --- a/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java @@ -54,502 +54,502 @@ import de.pixart.messenger.xmpp.jid.InvalidJidException; import de.pixart.messenger.xmpp.jid.Jid; public class ContactDetailsActivity extends OmemoActivity implements OnAccountUpdate, OnRosterUpdate, OnUpdateBlocklist, OnKeyStatusUpdated { - public static final String ACTION_VIEW_CONTACT = "view_contact"; - - private Conversation mConversation; - private Contact contact; - private DialogInterface.OnClickListener removeFromRoster = new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - xmppConnectionService.deleteContactOnServer(contact); - } - }; - private OnCheckedChangeListener mOnSendCheckedChange = new OnCheckedChangeListener() { - - @Override - public void onCheckedChanged(CompoundButton buttonView, - boolean isChecked) { - if (isChecked) { - if (contact - .getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { - xmppConnectionService.sendPresencePacket(contact - .getAccount(), - xmppConnectionService.getPresenceGenerator() - .sendPresenceUpdatesTo(contact)); - } else { - contact.setOption(Contact.Options.PREEMPTIVE_GRANT); - } - } else { - contact.resetOption(Contact.Options.PREEMPTIVE_GRANT); - xmppConnectionService.sendPresencePacket(contact.getAccount(), - xmppConnectionService.getPresenceGenerator() - .stopPresenceUpdatesTo(contact)); - } - } - }; - private OnCheckedChangeListener mOnReceiveCheckedChange = new OnCheckedChangeListener() { - - @Override - public void onCheckedChanged(CompoundButton buttonView, - boolean isChecked) { - if (isChecked) { - xmppConnectionService.sendPresencePacket(contact.getAccount(), - xmppConnectionService.getPresenceGenerator() - .requestPresenceUpdatesFrom(contact)); - } else { - xmppConnectionService.sendPresencePacket(contact.getAccount(), - xmppConnectionService.getPresenceGenerator() - .stopPresenceUpdatesFrom(contact)); - } - } - }; - private Jid accountJid; - private TextView lastseen; - private Jid contactJid; - private TextView contactJidTv; - private TextView accountJidTv; - private TextView statusMessage; - private CheckBox send; - private CheckBox receive; - private Button addContactButton; - private QuickContactBadge badge; - private LinearLayout keys; - private FlowLayout tags; - private boolean showDynamicTags = false; - private boolean showLastSeen = false; - private String messageFingerprint; - - private DialogInterface.OnClickListener addToPhonebook = new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); - intent.setType(Contacts.CONTENT_ITEM_TYPE); - intent.putExtra(Intents.Insert.IM_HANDLE, contact.getJid().toString()); - intent.putExtra(Intents.Insert.IM_PROTOCOL, - CommonDataKinds.Im.PROTOCOL_JABBER); - intent.putExtra("finishActivityOnSaveCompleted", true); - ContactDetailsActivity.this.startActivityForResult(intent, 0); - } - }; - - private OnClickListener onBadgeClick = new OnClickListener() { - - @Override - public void onClick(View v) { - Uri systemAccount = contact.getSystemAccount(); - if (systemAccount == null) { - AlertDialog.Builder builder = new AlertDialog.Builder( - ContactDetailsActivity.this); - builder.setTitle(getString(R.string.action_add_phone_book)); - builder.setMessage(getString(R.string.add_phone_book_text, - contact.getDisplayJid())); - builder.setNegativeButton(getString(R.string.cancel), null); - builder.setPositiveButton(getString(R.string.add), addToPhonebook); - builder.create().show(); - } else { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(systemAccount); - startActivity(intent); - } - } - }; - - @Override - public void onRosterUpdate() { - refreshUi(); - } - - @Override - public void onAccountUpdate() { - refreshUi(); - } - - @Override - public void OnUpdateBlocklist(final Status status) { - refreshUi(); - } - - @Override - protected void refreshUiReal() { - invalidateOptionsMenu(); - populateView(); - } - - @Override - protected String getShareableUri() { - if (contact != null) { - return "xmpp:"+contact.getJid().toBareJid().toString(); - } else { - return ""; - } - } - - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (getIntent().getAction().equals(ACTION_VIEW_CONTACT)) { - try { - this.accountJid = Jid.fromString(getIntent().getExtras().getString(EXTRA_ACCOUNT)); - } catch (final InvalidJidException ignored) { - } - try { - this.contactJid = Jid.fromString(getIntent().getExtras().getString("contact")); - } catch (final InvalidJidException ignored) { - } - } - this.messageFingerprint = getIntent().getStringExtra("fingerprint"); - setContentView(R.layout.activity_contact_details); - - contactJidTv = (TextView) findViewById(R.id.details_contactjid); - accountJidTv = (TextView) findViewById(R.id.details_account); - lastseen = (TextView) findViewById(R.id.details_lastseen); - statusMessage = (TextView) findViewById(R.id.status_message); - send = (CheckBox) findViewById(R.id.details_send_presence); - receive = (CheckBox) findViewById(R.id.details_receive_presence); - badge = (QuickContactBadge) findViewById(R.id.details_contact_badge); - addContactButton = (Button) findViewById(R.id.add_contact_button); - addContactButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View view) { - showAddToRosterDialog(contact); - } - }); - keys = (LinearLayout) findViewById(R.id.details_contact_keys); - tags = (FlowLayout) findViewById(R.id.tags); - if (getActionBar() != null) { - getActionBar().setHomeButtonEnabled(true); - getActionBar().setDisplayHomeAsUpEnabled(true); - } - } - - @Override - public void onStart() { - super.onStart(); - final int theme = findTheme(); - if (this.mTheme != theme) { - recreate(); - } else { - final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); - this.showDynamicTags = preferences.getBoolean("show_dynamic_tags", false); - this.showLastSeen = preferences.getBoolean("last_activity", false); - } - } - - @Override - public boolean onOptionsItemSelected(final MenuItem menuItem) { - final AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setNegativeButton(getString(R.string.cancel), null); - switch (menuItem.getItemId()) { - case android.R.id.home: - finish(); - break; - case R.id.action_share: - shareUri(); - break; - case R.id.action_delete_contact: - builder.setTitle(getString(R.string.action_delete_contact)) - .setMessage( - getString(R.string.remove_contact_text, - contact.getDisplayJid())) - .setPositiveButton(getString(R.string.delete), - removeFromRoster).create().show(); - break; - case R.id.action_edit_contact: - Uri systemAccount = contact.getSystemAccount(); - if (systemAccount == null) { - quickEdit(contact.getDisplayName(), 0, new OnValueEdited() { - - @Override - public void onValueEdited(String value) { - contact.setServerName(value); - ContactDetailsActivity.this.xmppConnectionService - .pushContactToServer(contact); - populateView(); - } - }); - } else { - Intent intent = new Intent(Intent.ACTION_EDIT); - intent.setDataAndType(systemAccount, Contacts.CONTENT_ITEM_TYPE); - intent.putExtra("finishActivityOnSaveCompleted", true); - startActivity(intent); - } - break; - case R.id.action_block: - BlockContactDialog.show(this, xmppConnectionService, contact); - break; - case R.id.action_unblock: - BlockContactDialog.show(this, xmppConnectionService, contact); - break; - } - return super.onOptionsItemSelected(menuItem); - } - - @Override - public boolean onCreateOptionsMenu(final Menu menu) { - getMenuInflater().inflate(R.menu.contact_details, menu); - MenuItem block = menu.findItem(R.id.action_block); - MenuItem unblock = menu.findItem(R.id.action_unblock); - MenuItem edit = menu.findItem(R.id.action_edit_contact); - MenuItem delete = menu.findItem(R.id.action_delete_contact); - if (contact == null) { - return true; - } - final XmppConnection connection = contact.getAccount().getXmppConnection(); - if (connection != null && connection.getFeatures().blocking()) { - if (this.contact.isBlocked()) { - block.setVisible(false); - } else { - unblock.setVisible(false); - } - } else { - unblock.setVisible(false); - block.setVisible(false); - } - if (!contact.showInRoster()) { - edit.setVisible(false); - delete.setVisible(false); - } - return super.onCreateOptionsMenu(menu); - } - - private void populateView() { - if (contact == null) { - return; - } - if (getActionBar() != null) { - final ActionBar ab = getActionBar(); - ab.setCustomView(R.layout.ab_title); - ab.setDisplayShowCustomEnabled(true); - TextView abtitle = (TextView) findViewById(android.R.id.text1); - TextView absubtitle = (TextView) findViewById(android.R.id.text2); - abtitle.setText(contact.getDisplayName()); - abtitle.setSelected(true); - abtitle.setClickable(false); - absubtitle.setVisibility(View.GONE); - absubtitle.setClickable(false); - } - - invalidateOptionsMenu(); - setTitle(contact.getDisplayName()); - if (contact.showInRoster()) { - send.setVisibility(View.VISIBLE); - receive.setVisibility(View.VISIBLE); - addContactButton.setVisibility(View.GONE); - send.setOnCheckedChangeListener(null); - receive.setOnCheckedChangeListener(null); - - List statusMessages = contact.getPresences().getStatusMessages(); - if (statusMessages.size() == 0) { - statusMessage.setVisibility(View.GONE); - } else { - StringBuilder builder = new StringBuilder(); - statusMessage.setVisibility(View.VISIBLE); - int s = statusMessages.size(); - for(int i = 0; i < s; ++i) { - if (s > 1) { - builder.append("• "); - } - builder.append(statusMessages.get(i)); - if (i < s - 1) { - builder.append("\n"); - } - } - statusMessage.setText(builder); - } - - if (contact.getOption(Contact.Options.FROM)) { - send.setText(R.string.send_presence_updates); - send.setChecked(true); - } else if (contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { - send.setChecked(false); - send.setText(R.string.send_presence_updates); - } else { - send.setText(R.string.preemptively_grant); - if (contact.getOption(Contact.Options.PREEMPTIVE_GRANT)) { - send.setChecked(true); - } else { - send.setChecked(false); - } - } - if (contact.getOption(Contact.Options.TO)) { - receive.setText(R.string.receive_presence_updates); - receive.setChecked(true); - } else { - receive.setText(R.string.ask_for_presence_updates); - if (contact.getOption(Contact.Options.ASKING)) { - receive.setChecked(true); - } else { - receive.setChecked(false); - } - } - if (contact.getAccount().isOnlineAndConnected()) { - receive.setEnabled(true); - send.setEnabled(true); - } else { - receive.setEnabled(false); - send.setEnabled(false); - } - send.setOnCheckedChangeListener(this.mOnSendCheckedChange); - receive.setOnCheckedChangeListener(this.mOnReceiveCheckedChange); - } else { - addContactButton.setVisibility(View.VISIBLE); - send.setVisibility(View.GONE); - receive.setVisibility(View.GONE); - statusMessage.setVisibility(View.GONE); - } - - if (contact.isBlocked() && !this.showDynamicTags) { - lastseen.setVisibility(View.VISIBLE); - lastseen.setText(R.string.contact_blocked); - } else { - if (showLastSeen && contact.getLastseen() > 0) { - lastseen.setVisibility(View.VISIBLE); - lastseen.setText(UIHelper.lastseen(getApplicationContext(), contact.isActive(), contact.getLastseen())); - } else { - lastseen.setVisibility(View.GONE); - } - } - - if (contact.getPresences().size() > 1) { - contactJidTv.setText(contact.getDisplayJid() + " (" - + contact.getPresences().size() + ")"); - } else { - contactJidTv.setText(contact.getDisplayJid()); - } - String account; - if (Config.DOMAIN_LOCK != null) { - account = contact.getAccount().getJid().getLocalpart(); - } else { - account = contact.getAccount().getJid().toBareJid().toString(); - } - accountJidTv.setText(getString(R.string.using_account, account)); - badge.setImageBitmap(avatarService().get(contact, getPixel(Config.AVATAR_SIZE))); - badge.setOnClickListener(this.onBadgeClick); - - keys.removeAllViews(); - boolean hasKeys = false; - LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); - if (Config.supportOtr()) { - for (final String otrFingerprint : contact.getOtrFingerprints()) { - hasKeys = true; - View view = inflater.inflate(R.layout.contact_key, keys, false); - TextView key = (TextView) view.findViewById(R.id.key); - TextView keyType = (TextView) view.findViewById(R.id.key_type); - ImageButton removeButton = (ImageButton) view - .findViewById(R.id.button_remove); - removeButton.setVisibility(View.VISIBLE); - key.setText(CryptoHelper.prettifyFingerprint(otrFingerprint)); - if (otrFingerprint != null && otrFingerprint.equals(messageFingerprint)) { - keyType.setText(R.string.otr_fingerprint_selected_message); - keyType.setTextColor(getResources().getColor(R.color.accent)); - } else { - keyType.setText(R.string.otr_fingerprint); - } - keys.addView(view); - removeButton.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - confirmToDeleteFingerprint(otrFingerprint); - } - }); - } - } - if (Config.supportOmemo()) { - for (final String fingerprint : contact.getAccount().getAxolotlService().getFingerprintsForContact(contact)) { - boolean highlight = fingerprint.equals(messageFingerprint); - hasKeys |= addFingerprintRow(keys, contact.getAccount(), fingerprint, highlight); - } - } - if (Config.supportOpenPgp() && contact.getPgpKeyId() != 0) { - hasKeys = true; - View view = inflater.inflate(R.layout.contact_key, keys, false); - TextView key = (TextView) view.findViewById(R.id.key); - TextView keyType = (TextView) view.findViewById(R.id.key_type); - keyType.setText(R.string.openpgp_key_id); - if ("pgp".equals(messageFingerprint)) { - keyType.setTextColor(getResources().getColor(R.color.accent)); - } - key.setText(OpenPgpUtils.convertKeyIdToHex(contact.getPgpKeyId())); - view.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - PgpEngine pgp = ContactDetailsActivity.this.xmppConnectionService - .getPgpEngine(); - if (pgp != null) { - PendingIntent intent = pgp.getIntentForKey(contact); - if (intent != null) { - try { - startIntentSenderForResult( - intent.getIntentSender(), 0, null, 0, - 0, 0); - } catch (SendIntentException e) { - - } - } - } - } - }); - keys.addView(view); - } - if (hasKeys) { - keys.setVisibility(View.VISIBLE); - } else { - keys.setVisibility(View.GONE); - } - - List tagList = contact.getTags(this); - if (tagList.size() == 0 || !this.showDynamicTags) { - tags.setVisibility(View.GONE); - } else { - tags.setVisibility(View.VISIBLE); - tags.removeAllViewsInLayout(); - for(final ListItem.Tag tag : tagList) { - final TextView tv = (TextView) inflater.inflate(R.layout.list_item_tag,tags,false); - tv.setText(tag.getName()); - tv.setBackgroundColor(tag.getColor()); - tags.addView(tv); - } - } - } - - protected void confirmToDeleteFingerprint(final String fingerprint) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.delete_fingerprint); - builder.setMessage(R.string.sure_delete_fingerprint); - builder.setNegativeButton(R.string.cancel, null); - builder.setPositiveButton(R.string.delete, - new android.content.DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - if (contact.deleteOtrFingerprint(fingerprint)) { - populateView(); - xmppConnectionService.syncRosterToDisk(contact.getAccount()); - } - } - - }); - builder.create().show(); - } - - public void onBackendConnected() { - if ((accountJid != null) && (contactJid != null)) { - Account account = xmppConnectionService - .findAccountByJid(accountJid); - if (account == null) { - return; - } - this.contact = account.getRoster().getContact(contactJid); - populateView(); - } - } - - @Override - public void onKeyStatusUpdated(AxolotlService.FetchStatus report) { - refreshUi(); - } + public static final String ACTION_VIEW_CONTACT = "view_contact"; + + private Conversation mConversation; + private Contact contact; + private DialogInterface.OnClickListener removeFromRoster = new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + xmppConnectionService.deleteContactOnServer(contact); + } + }; + private OnCheckedChangeListener mOnSendCheckedChange = new OnCheckedChangeListener() { + + @Override + public void onCheckedChanged(CompoundButton buttonView, + boolean isChecked) { + if (isChecked) { + if (contact + .getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { + xmppConnectionService.sendPresencePacket(contact + .getAccount(), + xmppConnectionService.getPresenceGenerator() + .sendPresenceUpdatesTo(contact)); + } else { + contact.setOption(Contact.Options.PREEMPTIVE_GRANT); + } + } else { + contact.resetOption(Contact.Options.PREEMPTIVE_GRANT); + xmppConnectionService.sendPresencePacket(contact.getAccount(), + xmppConnectionService.getPresenceGenerator() + .stopPresenceUpdatesTo(contact)); + } + } + }; + private OnCheckedChangeListener mOnReceiveCheckedChange = new OnCheckedChangeListener() { + + @Override + public void onCheckedChanged(CompoundButton buttonView, + boolean isChecked) { + if (isChecked) { + xmppConnectionService.sendPresencePacket(contact.getAccount(), + xmppConnectionService.getPresenceGenerator() + .requestPresenceUpdatesFrom(contact)); + } else { + xmppConnectionService.sendPresencePacket(contact.getAccount(), + xmppConnectionService.getPresenceGenerator() + .stopPresenceUpdatesFrom(contact)); + } + } + }; + private Jid accountJid; + private TextView lastseen; + private Jid contactJid; + private TextView contactJidTv; + private TextView accountJidTv; + private TextView statusMessage; + private CheckBox send; + private CheckBox receive; + private Button addContactButton; + private QuickContactBadge badge; + private LinearLayout keys; + private FlowLayout tags; + private boolean showDynamicTags = false; + private boolean showLastSeen = false; + private String messageFingerprint; + + private DialogInterface.OnClickListener addToPhonebook = new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); + intent.setType(Contacts.CONTENT_ITEM_TYPE); + intent.putExtra(Intents.Insert.IM_HANDLE, contact.getJid().toString()); + intent.putExtra(Intents.Insert.IM_PROTOCOL, + CommonDataKinds.Im.PROTOCOL_JABBER); + intent.putExtra("finishActivityOnSaveCompleted", true); + ContactDetailsActivity.this.startActivityForResult(intent, 0); + } + }; + + private OnClickListener onBadgeClick = new OnClickListener() { + + @Override + public void onClick(View v) { + Uri systemAccount = contact.getSystemAccount(); + if (systemAccount == null) { + AlertDialog.Builder builder = new AlertDialog.Builder( + ContactDetailsActivity.this); + builder.setTitle(getString(R.string.action_add_phone_book)); + builder.setMessage(getString(R.string.add_phone_book_text, + contact.getDisplayJid())); + builder.setNegativeButton(getString(R.string.cancel), null); + builder.setPositiveButton(getString(R.string.add), addToPhonebook); + builder.create().show(); + } else { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(systemAccount); + startActivity(intent); + } + } + }; + + @Override + public void onRosterUpdate() { + refreshUi(); + } + + @Override + public void onAccountUpdate() { + refreshUi(); + } + + @Override + public void OnUpdateBlocklist(final Status status) { + refreshUi(); + } + + @Override + protected void refreshUiReal() { + invalidateOptionsMenu(); + populateView(); + } + + @Override + protected String getShareableUri() { + if (contact != null) { + return "xmpp:" + contact.getJid().toBareJid().toString(); + } else { + return ""; + } + } + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getIntent().getAction().equals(ACTION_VIEW_CONTACT)) { + try { + this.accountJid = Jid.fromString(getIntent().getExtras().getString(EXTRA_ACCOUNT)); + } catch (final InvalidJidException ignored) { + } + try { + this.contactJid = Jid.fromString(getIntent().getExtras().getString("contact")); + } catch (final InvalidJidException ignored) { + } + } + this.messageFingerprint = getIntent().getStringExtra("fingerprint"); + setContentView(R.layout.activity_contact_details); + + contactJidTv = (TextView) findViewById(R.id.details_contactjid); + accountJidTv = (TextView) findViewById(R.id.details_account); + lastseen = (TextView) findViewById(R.id.details_lastseen); + statusMessage = (TextView) findViewById(R.id.status_message); + send = (CheckBox) findViewById(R.id.details_send_presence); + receive = (CheckBox) findViewById(R.id.details_receive_presence); + badge = (QuickContactBadge) findViewById(R.id.details_contact_badge); + addContactButton = (Button) findViewById(R.id.add_contact_button); + addContactButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + showAddToRosterDialog(contact); + } + }); + keys = (LinearLayout) findViewById(R.id.details_contact_keys); + tags = (FlowLayout) findViewById(R.id.tags); + if (getActionBar() != null) { + getActionBar().setHomeButtonEnabled(true); + getActionBar().setDisplayHomeAsUpEnabled(true); + } + } + + @Override + public void onStart() { + super.onStart(); + final int theme = findTheme(); + if (this.mTheme != theme) { + recreate(); + } else { + final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); + this.showDynamicTags = preferences.getBoolean("show_dynamic_tags", false); + this.showLastSeen = preferences.getBoolean("last_activity", false); + } + } + + @Override + public boolean onOptionsItemSelected(final MenuItem menuItem) { + final AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setNegativeButton(getString(R.string.cancel), null); + switch (menuItem.getItemId()) { + case android.R.id.home: + finish(); + break; + case R.id.action_share: + shareUri(); + break; + case R.id.action_delete_contact: + builder.setTitle(getString(R.string.action_delete_contact)) + .setMessage( + getString(R.string.remove_contact_text, + contact.getDisplayJid())) + .setPositiveButton(getString(R.string.delete), + removeFromRoster).create().show(); + break; + case R.id.action_edit_contact: + Uri systemAccount = contact.getSystemAccount(); + if (systemAccount == null) { + quickEdit(contact.getDisplayName(), 0, new OnValueEdited() { + + @Override + public void onValueEdited(String value) { + contact.setServerName(value); + ContactDetailsActivity.this.xmppConnectionService + .pushContactToServer(contact); + populateView(); + } + }); + } else { + Intent intent = new Intent(Intent.ACTION_EDIT); + intent.setDataAndType(systemAccount, Contacts.CONTENT_ITEM_TYPE); + intent.putExtra("finishActivityOnSaveCompleted", true); + startActivity(intent); + } + break; + case R.id.action_block: + BlockContactDialog.show(this, xmppConnectionService, contact); + break; + case R.id.action_unblock: + BlockContactDialog.show(this, xmppConnectionService, contact); + break; + } + return super.onOptionsItemSelected(menuItem); + } + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + getMenuInflater().inflate(R.menu.contact_details, menu); + MenuItem block = menu.findItem(R.id.action_block); + MenuItem unblock = menu.findItem(R.id.action_unblock); + MenuItem edit = menu.findItem(R.id.action_edit_contact); + MenuItem delete = menu.findItem(R.id.action_delete_contact); + if (contact == null) { + return true; + } + final XmppConnection connection = contact.getAccount().getXmppConnection(); + if (connection != null && connection.getFeatures().blocking()) { + if (this.contact.isBlocked()) { + block.setVisible(false); + } else { + unblock.setVisible(false); + } + } else { + unblock.setVisible(false); + block.setVisible(false); + } + if (!contact.showInRoster()) { + edit.setVisible(false); + delete.setVisible(false); + } + return super.onCreateOptionsMenu(menu); + } + + private void populateView() { + if (contact == null) { + return; + } + if (getActionBar() != null) { + final ActionBar ab = getActionBar(); + ab.setCustomView(R.layout.ab_title); + ab.setDisplayShowCustomEnabled(true); + TextView abtitle = (TextView) findViewById(android.R.id.text1); + TextView absubtitle = (TextView) findViewById(android.R.id.text2); + abtitle.setText(contact.getDisplayName()); + abtitle.setSelected(true); + abtitle.setClickable(false); + absubtitle.setVisibility(View.GONE); + absubtitle.setClickable(false); + } + + invalidateOptionsMenu(); + setTitle(contact.getDisplayName()); + if (contact.showInRoster()) { + send.setVisibility(View.VISIBLE); + receive.setVisibility(View.VISIBLE); + addContactButton.setVisibility(View.GONE); + send.setOnCheckedChangeListener(null); + receive.setOnCheckedChangeListener(null); + + List statusMessages = contact.getPresences().getStatusMessages(); + if (statusMessages.size() == 0) { + statusMessage.setVisibility(View.GONE); + } else { + StringBuilder builder = new StringBuilder(); + statusMessage.setVisibility(View.VISIBLE); + int s = statusMessages.size(); + for (int i = 0; i < s; ++i) { + if (s > 1) { + builder.append("• "); + } + builder.append(statusMessages.get(i)); + if (i < s - 1) { + builder.append("\n"); + } + } + statusMessage.setText(builder); + } + + if (contact.getOption(Contact.Options.FROM)) { + send.setText(R.string.send_presence_updates); + send.setChecked(true); + } else if (contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { + send.setChecked(false); + send.setText(R.string.send_presence_updates); + } else { + send.setText(R.string.preemptively_grant); + if (contact.getOption(Contact.Options.PREEMPTIVE_GRANT)) { + send.setChecked(true); + } else { + send.setChecked(false); + } + } + if (contact.getOption(Contact.Options.TO)) { + receive.setText(R.string.receive_presence_updates); + receive.setChecked(true); + } else { + receive.setText(R.string.ask_for_presence_updates); + if (contact.getOption(Contact.Options.ASKING)) { + receive.setChecked(true); + } else { + receive.setChecked(false); + } + } + if (contact.getAccount().isOnlineAndConnected()) { + receive.setEnabled(true); + send.setEnabled(true); + } else { + receive.setEnabled(false); + send.setEnabled(false); + } + send.setOnCheckedChangeListener(this.mOnSendCheckedChange); + receive.setOnCheckedChangeListener(this.mOnReceiveCheckedChange); + } else { + addContactButton.setVisibility(View.VISIBLE); + send.setVisibility(View.GONE); + receive.setVisibility(View.GONE); + statusMessage.setVisibility(View.GONE); + } + + if (contact.isBlocked() && !this.showDynamicTags) { + lastseen.setVisibility(View.VISIBLE); + lastseen.setText(R.string.contact_blocked); + } else { + if (showLastSeen && contact.getLastseen() > 0) { + lastseen.setVisibility(View.VISIBLE); + lastseen.setText(UIHelper.lastseen(getApplicationContext(), contact.isActive(), contact.getLastseen())); + } else { + lastseen.setVisibility(View.GONE); + } + } + + if (contact.getPresences().size() > 1) { + contactJidTv.setText(contact.getDisplayJid() + " (" + + contact.getPresences().size() + ")"); + } else { + contactJidTv.setText(contact.getDisplayJid()); + } + String account; + if (Config.DOMAIN_LOCK != null) { + account = contact.getAccount().getJid().getLocalpart(); + } else { + account = contact.getAccount().getJid().toBareJid().toString(); + } + accountJidTv.setText(getString(R.string.using_account, account)); + badge.setImageBitmap(avatarService().get(contact, getPixel(Config.AVATAR_SIZE))); + badge.setOnClickListener(this.onBadgeClick); + + keys.removeAllViews(); + boolean hasKeys = false; + LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); + if (Config.supportOtr()) { + for (final String otrFingerprint : contact.getOtrFingerprints()) { + hasKeys = true; + View view = inflater.inflate(R.layout.contact_key, keys, false); + TextView key = (TextView) view.findViewById(R.id.key); + TextView keyType = (TextView) view.findViewById(R.id.key_type); + ImageButton removeButton = (ImageButton) view + .findViewById(R.id.button_remove); + removeButton.setVisibility(View.VISIBLE); + key.setText(CryptoHelper.prettifyFingerprint(otrFingerprint)); + if (otrFingerprint != null && otrFingerprint.equals(messageFingerprint)) { + keyType.setText(R.string.otr_fingerprint_selected_message); + keyType.setTextColor(getResources().getColor(R.color.accent)); + } else { + keyType.setText(R.string.otr_fingerprint); + } + keys.addView(view); + removeButton.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + confirmToDeleteFingerprint(otrFingerprint); + } + }); + } + } + if (Config.supportOmemo()) { + for (final String fingerprint : contact.getAccount().getAxolotlService().getFingerprintsForContact(contact)) { + boolean highlight = fingerprint.equals(messageFingerprint); + hasKeys |= addFingerprintRow(keys, contact.getAccount(), fingerprint, highlight); + } + } + if (Config.supportOpenPgp() && contact.getPgpKeyId() != 0) { + hasKeys = true; + View view = inflater.inflate(R.layout.contact_key, keys, false); + TextView key = (TextView) view.findViewById(R.id.key); + TextView keyType = (TextView) view.findViewById(R.id.key_type); + keyType.setText(R.string.openpgp_key_id); + if ("pgp".equals(messageFingerprint)) { + keyType.setTextColor(getResources().getColor(R.color.accent)); + } + key.setText(OpenPgpUtils.convertKeyIdToHex(contact.getPgpKeyId())); + view.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + PgpEngine pgp = ContactDetailsActivity.this.xmppConnectionService + .getPgpEngine(); + if (pgp != null) { + PendingIntent intent = pgp.getIntentForKey(contact); + if (intent != null) { + try { + startIntentSenderForResult( + intent.getIntentSender(), 0, null, 0, + 0, 0); + } catch (SendIntentException e) { + + } + } + } + } + }); + keys.addView(view); + } + if (hasKeys) { + keys.setVisibility(View.VISIBLE); + } else { + keys.setVisibility(View.GONE); + } + + List tagList = contact.getTags(this); + if (tagList.size() == 0 || !this.showDynamicTags) { + tags.setVisibility(View.GONE); + } else { + tags.setVisibility(View.VISIBLE); + tags.removeAllViewsInLayout(); + for (final ListItem.Tag tag : tagList) { + final TextView tv = (TextView) inflater.inflate(R.layout.list_item_tag, tags, false); + tv.setText(tag.getName()); + tv.setBackgroundColor(tag.getColor()); + tags.addView(tv); + } + } + } + + protected void confirmToDeleteFingerprint(final String fingerprint) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.delete_fingerprint); + builder.setMessage(R.string.sure_delete_fingerprint); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(R.string.delete, + new android.content.DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + if (contact.deleteOtrFingerprint(fingerprint)) { + populateView(); + xmppConnectionService.syncRosterToDisk(contact.getAccount()); + } + } + + }); + builder.create().show(); + } + + public void onBackendConnected() { + if ((accountJid != null) && (contactJid != null)) { + Account account = xmppConnectionService + .findAccountByJid(accountJid); + if (account == null) { + return; + } + this.contact = account.getRoster().getContact(contactJid); + populateView(); + } + } + + @Override + public void onKeyStatusUpdated(AxolotlService.FetchStatus report) { + refreshUi(); + } } diff --git a/src/main/java/de/pixart/messenger/ui/ConversationActivity.java b/src/main/java/de/pixart/messenger/ui/ConversationActivity.java index 1fb23ff69..47992b221 100644 --- a/src/main/java/de/pixart/messenger/ui/ConversationActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ConversationActivity.java @@ -86,119 +86,119 @@ import de.pixart.messenger.xmpp.jid.Jid; import de.timroes.android.listview.EnhancedListView; public class ConversationActivity extends XmppActivity - implements OnAccountUpdate, OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast, View.OnClickListener { - - public static final String ACTION_VIEW_CONVERSATION = "de.pixart.messenger.VIEW"; - public static final String CONVERSATION = "conversationUuid"; - public static final String EXTRA_DOWNLOAD_UUID = "de.pixart.messenger.download_uuid"; - public static final String TEXT = "text"; - public static final String NICK = "nick"; - public static final String PRIVATE_MESSAGE = "pm"; - - public static final int REQUEST_SEND_MESSAGE = 0x0201; - public static final int REQUEST_DECRYPT_PGP = 0x0202; - public static final int REQUEST_ENCRYPT_MESSAGE = 0x0207; - public static final int REQUEST_TRUST_KEYS_TEXT = 0x0208; - public static final int REQUEST_TRUST_KEYS_MENU = 0x0209; - public static final int REQUEST_START_DOWNLOAD = 0x0210; - public static final int ATTACHMENT_CHOICE_CHOOSE_IMAGE = 0x0301; - public static final int ATTACHMENT_CHOICE_TAKE_PHOTO = 0x0302; - public static final int ATTACHMENT_CHOICE_CHOOSE_FILE = 0x0303; - public static final int ATTACHMENT_CHOICE_RECORD_VOICE = 0x0304; - public static final int ATTACHMENT_CHOICE_LOCATION = 0x0305; + implements OnAccountUpdate, OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast, View.OnClickListener { + + public static final String ACTION_VIEW_CONVERSATION = "de.pixart.messenger.VIEW"; + public static final String CONVERSATION = "conversationUuid"; + public static final String EXTRA_DOWNLOAD_UUID = "de.pixart.messenger.download_uuid"; + public static final String TEXT = "text"; + public static final String NICK = "nick"; + public static final String PRIVATE_MESSAGE = "pm"; + + public static final int REQUEST_SEND_MESSAGE = 0x0201; + public static final int REQUEST_DECRYPT_PGP = 0x0202; + public static final int REQUEST_ENCRYPT_MESSAGE = 0x0207; + public static final int REQUEST_TRUST_KEYS_TEXT = 0x0208; + public static final int REQUEST_TRUST_KEYS_MENU = 0x0209; + public static final int REQUEST_START_DOWNLOAD = 0x0210; + public static final int ATTACHMENT_CHOICE_CHOOSE_IMAGE = 0x0301; + public static final int ATTACHMENT_CHOICE_TAKE_PHOTO = 0x0302; + public static final int ATTACHMENT_CHOICE_CHOOSE_FILE = 0x0303; + public static final int ATTACHMENT_CHOICE_RECORD_VOICE = 0x0304; + public static final int ATTACHMENT_CHOICE_LOCATION = 0x0305; public static final int ATTACHMENT_CHOICE_CHOOSE_VIDEO = 0x0306; public static final int ATTACHMENT_CHOICE_INVALID = 0x0399; - private static final String STATE_OPEN_CONVERSATION = "state_open_conversation"; - private static final String STATE_PANEL_OPEN = "state_panel_open"; - private static final String STATE_PENDING_IMAGE_URI = "state_pending_image_uri"; + private static final String STATE_OPEN_CONVERSATION = "state_open_conversation"; + private static final String STATE_PANEL_OPEN = "state_panel_open"; + private static final String STATE_PENDING_IMAGE_URI = "state_pending_image_uri"; private static final String STATE_PENDING_PHOTO_URI = "state_pending_photo_uri"; private static final String STATE_FIRST_VISIBLE = "first_visible"; private static final String STATE_OFFSET_FROM_TOP = "offset_from_top"; - final private List mPendingImageUris = new ArrayList<>(); + final private List mPendingImageUris = new ArrayList<>(); final private List mPendingPhotoUris = new ArrayList<>(); - final private List mPendingFileUris = new ArrayList<>(); + final private List mPendingFileUris = new ArrayList<>(); final private List mPendingVideoUris = new ArrayList<>(); - private String mOpenConversation = null; - private boolean mPanelOpen = true; - private Pair mScrollPosition = null; - private Uri mPendingGeoUri = null; - private boolean forbidProcessingPendings = false; - private Message mPendingDownloadableMessage = null; + private String mOpenConversation = null; + private boolean mPanelOpen = true; + private Pair mScrollPosition = null; + private Uri mPendingGeoUri = null; + private boolean forbidProcessingPendings = false; + private Message mPendingDownloadableMessage = null; - private boolean conversationWasSelectedByKeyboard = false; + private boolean conversationWasSelectedByKeyboard = false; - private View mContentView; + private View mContentView; - private List conversationList = new ArrayList<>(); - private Conversation swipedConversation = null; - private Conversation mSelectedConversation = null; - private EnhancedListView listView; - private ConversationFragment mConversationFragment; + private List conversationList = new ArrayList<>(); + private Conversation swipedConversation = null; + private Conversation mSelectedConversation = null; + private EnhancedListView listView; + private ConversationFragment mConversationFragment; - private ArrayAdapter listAdapter; + private ArrayAdapter listAdapter; - private boolean mActivityPaused = false; - private AtomicBoolean mRedirected = new AtomicBoolean(false); - private Pair mPostponedActivityResult; + private boolean mActivityPaused = false; + private AtomicBoolean mRedirected = new AtomicBoolean(false); + private Pair mPostponedActivityResult; private boolean mUnprocessedNewIntent = false; long FirstStartTime = -1; - public Conversation getSelectedConversation() { - return this.mSelectedConversation; - } - - public void setSelectedConversation(Conversation conversation) { - this.mSelectedConversation = conversation; - } - - public void showConversationsOverview() { - if (mContentView instanceof SlidingPaneLayout) { - SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView; - mSlidingPaneLayout.openPane(); - } - } - - @Override - protected String getShareableUri() { - Conversation conversation = getSelectedConversation(); - if (conversation != null) { - return conversation.getAccount().getShareableUri(); - } else { - return ""; - } - } - - public void hideConversationsOverview() { - if (mContentView instanceof SlidingPaneLayout) { - SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView; - mSlidingPaneLayout.closePane(); - } - } - - public boolean isConversationsOverviewHideable() { - if (mContentView instanceof SlidingPaneLayout) { - return true; - } else { - return false; - } - } - - public boolean isConversationsOverviewVisable() { - if (mContentView instanceof SlidingPaneLayout) { - SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView; - return mSlidingPaneLayout.isOpen(); - } else { - return true; - } - } - - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (savedInstanceState != null) { - mOpenConversation = savedInstanceState.getString(STATE_OPEN_CONVERSATION, null); - mPanelOpen = savedInstanceState.getBoolean(STATE_PANEL_OPEN, true); + public Conversation getSelectedConversation() { + return this.mSelectedConversation; + } + + public void setSelectedConversation(Conversation conversation) { + this.mSelectedConversation = conversation; + } + + public void showConversationsOverview() { + if (mContentView instanceof SlidingPaneLayout) { + SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView; + mSlidingPaneLayout.openPane(); + } + } + + @Override + protected String getShareableUri() { + Conversation conversation = getSelectedConversation(); + if (conversation != null) { + return conversation.getAccount().getShareableUri(); + } else { + return ""; + } + } + + public void hideConversationsOverview() { + if (mContentView instanceof SlidingPaneLayout) { + SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView; + mSlidingPaneLayout.closePane(); + } + } + + public boolean isConversationsOverviewHideable() { + if (mContentView instanceof SlidingPaneLayout) { + return true; + } else { + return false; + } + } + + public boolean isConversationsOverviewVisable() { + if (mContentView instanceof SlidingPaneLayout) { + SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView; + return mSlidingPaneLayout.isOpen(); + } else { + return true; + } + } + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (savedInstanceState != null) { + mOpenConversation = savedInstanceState.getString(STATE_OPEN_CONVERSATION, null); + mPanelOpen = savedInstanceState.getBoolean(STATE_PANEL_OPEN, true); int pos = savedInstanceState.getInt(STATE_FIRST_VISIBLE, -1); int offset = savedInstanceState.getInt(STATE_OFFSET_FROM_TOP, 1); if (pos >= 0 && offset <= 0) { @@ -208,166 +208,166 @@ public class ConversationActivity extends XmppActivity mScrollPosition = null; } String pending_image = savedInstanceState.getString(STATE_PENDING_IMAGE_URI, null); - if (pending_image != null) { - Log.d(Config.LOGTAG,"ConversationActivity.onCreate() - restoring pending image uri"); - mPendingImageUris.clear(); - mPendingImageUris.add(Uri.parse(pending_image)); - } + if (pending_image != null) { + Log.d(Config.LOGTAG, "ConversationActivity.onCreate() - restoring pending image uri"); + mPendingImageUris.clear(); + mPendingImageUris.add(Uri.parse(pending_image)); + } String pending_photo = savedInstanceState.getString(STATE_PENDING_PHOTO_URI, null); if (pending_photo != null) { - Log.d(Config.LOGTAG,"ConversationActivity.onCreate() - restoring pending photo uri"); + Log.d(Config.LOGTAG, "ConversationActivity.onCreate() - restoring pending photo uri"); mPendingPhotoUris.clear(); mPendingPhotoUris.add(Uri.parse(pending_photo)); } - } + } - setContentView(R.layout.fragment_conversations_overview); + setContentView(R.layout.fragment_conversations_overview); this.mConversationFragment = new ConversationFragment(); - FragmentTransaction transaction = getFragmentManager().beginTransaction(); - transaction.replace(R.id.selected_conversation, this.mConversationFragment, "conversation"); - transaction.commit(); - - listView = (EnhancedListView) findViewById(R.id.list); - this.listAdapter = new ConversationAdapter(this, conversationList); - listView.setAdapter(this.listAdapter); - - if (getActionBar() != null) { - getActionBar().setDisplayHomeAsUpEnabled(false); - getActionBar().setHomeButtonEnabled(false); - } - - listView.setOnItemClickListener(new OnItemClickListener() { - - @Override - public void onItemClick(AdapterView arg0, View clickedView, - int position, long arg3) { - if (getSelectedConversation() != conversationList.get(position)) { - setSelectedConversation(conversationList.get(position)); - ConversationActivity.this.mConversationFragment.reInit(getSelectedConversation()); - conversationWasSelectedByKeyboard = false; - } - hideConversationsOverview(); - openConversation(); - } - }); - - listView.setDismissCallback(new EnhancedListView.OnDismissCallback() { - - @Override - public EnhancedListView.Undoable onDismiss(final EnhancedListView enhancedListView, final int position) { - - final int index = listView.getFirstVisiblePosition(); - View v = listView.getChildAt(0); - final int top = (v == null) ? 0 : (v.getTop() - listView.getPaddingTop()); - - try { - swipedConversation = listAdapter.getItem(position); - } catch (IndexOutOfBoundsException e) { - return null; - } - listAdapter.remove(swipedConversation); - xmppConnectionService.markRead(swipedConversation); - - final boolean formerlySelected = (getSelectedConversation() == swipedConversation); - if (position == 0 && listAdapter.getCount() == 0) { - endConversation(swipedConversation, false, true); - return null; - } else if (formerlySelected) { - setSelectedConversation(listAdapter.getItem(0)); - ConversationActivity.this.mConversationFragment - .reInit(getSelectedConversation()); - } - - return new EnhancedListView.Undoable() { - - @Override - public void undo() { - listAdapter.insert(swipedConversation, position); - if (formerlySelected) { - setSelectedConversation(swipedConversation); - ConversationActivity.this.mConversationFragment - .reInit(getSelectedConversation()); - } - swipedConversation = null; - listView.setSelectionFromTop(index + (listView.getChildCount() < position ? 1 : 0), top); - } - - @Override - public void discard() { - if (!swipedConversation.isRead() - && swipedConversation.getMode() == Conversation.MODE_SINGLE) { - swipedConversation = null; - return; - } - endConversation(swipedConversation, false, false); - swipedConversation = null; - } - - @Override - public String getTitle() { - if (swipedConversation.getMode() == Conversation.MODE_MULTI) { - return getResources().getString(R.string.title_undo_swipe_out_muc); - } else { - return getResources().getString(R.string.title_undo_swipe_out_conversation); - } - } - }; - } - }); - //listView.enableSwipeToDismiss(); - listView.setSwipingLayout(R.id.swipeable_item); - listView.setUndoStyle(EnhancedListView.UndoStyle.SINGLE_POPUP); - listView.setUndoHideDelay(10000); - listView.setRequireTouchBeforeDismiss(false); - listView.setSwipeDirection(EnhancedListView.SwipeDirection.START); // swipe to left to close conversation - - mContentView = findViewById(R.id.content_view_spl); - if (mContentView == null) { - mContentView = findViewById(R.id.content_view_ll); - } - if (mContentView instanceof SlidingPaneLayout) { - SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView; - mSlidingPaneLayout.setParallaxDistance(150); - mSlidingPaneLayout - .setShadowResourceLeft(R.drawable.es_slidingpane_shadow); - mSlidingPaneLayout.setSliderFadeColor(0); - mSlidingPaneLayout.setPanelSlideListener(new PanelSlideListener() { - - @Override - public void onPanelOpened(View arg0) { - updateActionBarTitle(); - invalidateOptionsMenu(); - hideKeyboard(); - if (xmppConnectionServiceBound) { - xmppConnectionService.getNotificationService() - .setOpenConversation(null); - } - closeContextMenu(); - } - - @Override - public void onPanelClosed(View arg0) { - listView.discardUndo(); - openConversation(); - } - - @Override - public void onPanelSlide(View arg0, float arg1) { - // TODO Auto-generated method stub - - } - }); - } - } - - private boolean isPackageInstalled(String targetPackage){ + FragmentTransaction transaction = getFragmentManager().beginTransaction(); + transaction.replace(R.id.selected_conversation, this.mConversationFragment, "conversation"); + transaction.commit(); + + listView = (EnhancedListView) findViewById(R.id.list); + this.listAdapter = new ConversationAdapter(this, conversationList); + listView.setAdapter(this.listAdapter); + + if (getActionBar() != null) { + getActionBar().setDisplayHomeAsUpEnabled(false); + getActionBar().setHomeButtonEnabled(false); + } + + listView.setOnItemClickListener(new OnItemClickListener() { + + @Override + public void onItemClick(AdapterView arg0, View clickedView, + int position, long arg3) { + if (getSelectedConversation() != conversationList.get(position)) { + setSelectedConversation(conversationList.get(position)); + ConversationActivity.this.mConversationFragment.reInit(getSelectedConversation()); + conversationWasSelectedByKeyboard = false; + } + hideConversationsOverview(); + openConversation(); + } + }); + + listView.setDismissCallback(new EnhancedListView.OnDismissCallback() { + + @Override + public EnhancedListView.Undoable onDismiss(final EnhancedListView enhancedListView, final int position) { + + final int index = listView.getFirstVisiblePosition(); + View v = listView.getChildAt(0); + final int top = (v == null) ? 0 : (v.getTop() - listView.getPaddingTop()); + + try { + swipedConversation = listAdapter.getItem(position); + } catch (IndexOutOfBoundsException e) { + return null; + } + listAdapter.remove(swipedConversation); + xmppConnectionService.markRead(swipedConversation); + + final boolean formerlySelected = (getSelectedConversation() == swipedConversation); + if (position == 0 && listAdapter.getCount() == 0) { + endConversation(swipedConversation, false, true); + return null; + } else if (formerlySelected) { + setSelectedConversation(listAdapter.getItem(0)); + ConversationActivity.this.mConversationFragment + .reInit(getSelectedConversation()); + } + + return new EnhancedListView.Undoable() { + + @Override + public void undo() { + listAdapter.insert(swipedConversation, position); + if (formerlySelected) { + setSelectedConversation(swipedConversation); + ConversationActivity.this.mConversationFragment + .reInit(getSelectedConversation()); + } + swipedConversation = null; + listView.setSelectionFromTop(index + (listView.getChildCount() < position ? 1 : 0), top); + } + + @Override + public void discard() { + if (!swipedConversation.isRead() + && swipedConversation.getMode() == Conversation.MODE_SINGLE) { + swipedConversation = null; + return; + } + endConversation(swipedConversation, false, false); + swipedConversation = null; + } + + @Override + public String getTitle() { + if (swipedConversation.getMode() == Conversation.MODE_MULTI) { + return getResources().getString(R.string.title_undo_swipe_out_muc); + } else { + return getResources().getString(R.string.title_undo_swipe_out_conversation); + } + } + }; + } + }); + //listView.enableSwipeToDismiss(); + listView.setSwipingLayout(R.id.swipeable_item); + listView.setUndoStyle(EnhancedListView.UndoStyle.SINGLE_POPUP); + listView.setUndoHideDelay(10000); + listView.setRequireTouchBeforeDismiss(false); + listView.setSwipeDirection(EnhancedListView.SwipeDirection.START); // swipe to left to close conversation + + mContentView = findViewById(R.id.content_view_spl); + if (mContentView == null) { + mContentView = findViewById(R.id.content_view_ll); + } + if (mContentView instanceof SlidingPaneLayout) { + SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView; + mSlidingPaneLayout.setParallaxDistance(150); + mSlidingPaneLayout + .setShadowResourceLeft(R.drawable.es_slidingpane_shadow); + mSlidingPaneLayout.setSliderFadeColor(0); + mSlidingPaneLayout.setPanelSlideListener(new PanelSlideListener() { + + @Override + public void onPanelOpened(View arg0) { + updateActionBarTitle(); + invalidateOptionsMenu(); + hideKeyboard(); + if (xmppConnectionServiceBound) { + xmppConnectionService.getNotificationService() + .setOpenConversation(null); + } + closeContextMenu(); + } + + @Override + public void onPanelClosed(View arg0) { + listView.discardUndo(); + openConversation(); + } + + @Override + public void onPanelSlide(View arg0, float arg1) { + // TODO Auto-generated method stub + + } + }); + } + } + + private boolean isPackageInstalled(String targetPackage) { List packages; PackageManager pm; pm = getPackageManager(); packages = pm.getInstalledApplications(0); for (ApplicationInfo packageInfo : packages) { - if(packageInfo.packageName.equals(targetPackage)) return true; + if (packageInfo.packageName.equals(targetPackage)) return true; } return false; } @@ -454,36 +454,36 @@ public class ConversationActivity extends XmppActivity } } - @Override - public void switchToConversation(Conversation conversation) { - setSelectedConversation(conversation); - runOnUiThread(new Runnable() { - @Override - public void run() { - ConversationActivity.this.mConversationFragment.reInit(getSelectedConversation()); - openConversation(); - } - }); - } + @Override + public void switchToConversation(Conversation conversation) { + setSelectedConversation(conversation); + runOnUiThread(new Runnable() { + @Override + public void run() { + ConversationActivity.this.mConversationFragment.reInit(getSelectedConversation()); + openConversation(); + } + }); + } - private void updateActionBarTitle() { - updateActionBarTitle(isConversationsOverviewHideable() && !isConversationsOverviewVisable()); - } + private void updateActionBarTitle() { + updateActionBarTitle(isConversationsOverviewHideable() && !isConversationsOverviewVisable()); + } - private void updateActionBarTitle(boolean titleShouldBeName) { + private void updateActionBarTitle(boolean titleShouldBeName) { final ActionBar ab = getActionBar(); final Conversation conversation = getSelectedConversation(); if (ab != null) { if (titleShouldBeName && conversation != null) { ab.setDisplayHomeAsUpEnabled(true); ab.setHomeButtonEnabled(true); - ab.setDisplayShowTitleEnabled(false); - ab.setDisplayShowCustomEnabled(true); - ab.setCustomView(R.layout.ab_title); + ab.setDisplayShowTitleEnabled(false); + ab.setDisplayShowCustomEnabled(true); + ab.setCustomView(R.layout.ab_title); TextView abtitle = (TextView) findViewById(android.R.id.text1); TextView absubtitle = (TextView) findViewById(android.R.id.text2); if (conversation.getMode() == Conversation.MODE_SINGLE || useSubjectToIdentifyConference()) { - abtitle.setText(conversation.getName()); + abtitle.setText(conversation.getName()); abtitle.setOnClickListener(this); abtitle.setSelected(true); if (conversation.getMode() == Conversation.MODE_SINGLE && !this.getSelectedConversation().withSelf()) { @@ -510,14 +510,14 @@ public class ConversationActivity extends XmppActivity } } else if (useSubjectToIdentifyConference()) { if (conversation.getParticipants() != null) { - absubtitle.setText(conversation.getParticipants()); + absubtitle.setText(conversation.getParticipants()); absubtitle.setSelected(true); - absubtitle.setOnClickListener(this); - } else { + absubtitle.setOnClickListener(this); + } else { absubtitle.setText(R.string.no_participants); abtitle.setSelected(true); absubtitle.setOnClickListener(this); - } + } } } else { abtitle.setText(conversation.getJid().toBareJid().toString()); @@ -537,206 +537,206 @@ public class ConversationActivity extends XmppActivity } } - private void openConversation() { - this.updateActionBarTitle(); - this.invalidateOptionsMenu(); - if (xmppConnectionServiceBound) { - final Conversation conversation = getSelectedConversation(); - xmppConnectionService.getNotificationService().setOpenConversation(conversation); - sendReadMarkerIfNecessary(conversation); - } - listAdapter.notifyDataSetChanged(); - } + private void openConversation() { + this.updateActionBarTitle(); + this.invalidateOptionsMenu(); + if (xmppConnectionServiceBound) { + final Conversation conversation = getSelectedConversation(); + xmppConnectionService.getNotificationService().setOpenConversation(conversation); + sendReadMarkerIfNecessary(conversation); + } + listAdapter.notifyDataSetChanged(); + } - public void sendReadMarkerIfNecessary(final Conversation conversation) { - if (!mActivityPaused && !mUnprocessedNewIntent && conversation != null) { - xmppConnectionService.sendReadMarker(conversation); - } - } + public void sendReadMarkerIfNecessary(final Conversation conversation) { + if (!mActivityPaused && !mUnprocessedNewIntent && conversation != null) { + xmppConnectionService.sendReadMarker(conversation); + } + } - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.conversations, menu); - final MenuItem menuSecure = menu.findItem(R.id.action_security); - final MenuItem menuArchiveChat = menu.findItem(R.id.action_archive_chat); + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.conversations, menu); + final MenuItem menuSecure = menu.findItem(R.id.action_security); + final MenuItem menuArchiveChat = menu.findItem(R.id.action_archive_chat); final MenuItem menuArchiveMuc = menu.findItem(R.id.action_archive_muc); - final MenuItem menuAttach = menu.findItem(R.id.action_attach_file); - final MenuItem menuClearHistory = menu.findItem(R.id.action_clear_history); - final MenuItem menuAdd = menu.findItem(R.id.action_add); - final MenuItem menuInviteContact = menu.findItem(R.id.action_invite); - final MenuItem menuMute = menu.findItem(R.id.action_mute); - final MenuItem menuUnmute = menu.findItem(R.id.action_unmute); - final MenuItem menuUpdater = menu.findItem(R.id.action_check_updates); - final MenuItem menuInviteUser = menu.findItem(R.id.action_invite_user); - - if (isConversationsOverviewVisable() && isConversationsOverviewHideable()) { - menuArchiveChat.setVisible(false); + final MenuItem menuAttach = menu.findItem(R.id.action_attach_file); + final MenuItem menuClearHistory = menu.findItem(R.id.action_clear_history); + final MenuItem menuAdd = menu.findItem(R.id.action_add); + final MenuItem menuInviteContact = menu.findItem(R.id.action_invite); + final MenuItem menuMute = menu.findItem(R.id.action_mute); + final MenuItem menuUnmute = menu.findItem(R.id.action_unmute); + final MenuItem menuUpdater = menu.findItem(R.id.action_check_updates); + final MenuItem menuInviteUser = menu.findItem(R.id.action_invite_user); + + if (isConversationsOverviewVisable() && isConversationsOverviewHideable()) { + menuArchiveChat.setVisible(false); menuArchiveMuc.setVisible(false); - menuSecure.setVisible(false); - menuInviteContact.setVisible(false); - menuAttach.setVisible(false); - menuClearHistory.setVisible(false); - menuMute.setVisible(false); - menuUnmute.setVisible(false); - } else { - menuAdd.setVisible(!isConversationsOverviewHideable()); - //hide settings, accounts and updater in all menus except in main window - menuUpdater.setVisible(false); - menuInviteUser.setVisible(false); - - if (this.getSelectedConversation() != null) { + menuSecure.setVisible(false); + menuInviteContact.setVisible(false); + menuAttach.setVisible(false); + menuClearHistory.setVisible(false); + menuMute.setVisible(false); + menuUnmute.setVisible(false); + } else { + menuAdd.setVisible(!isConversationsOverviewHideable()); + //hide settings, accounts and updater in all menus except in main window + menuUpdater.setVisible(false); + menuInviteUser.setVisible(false); + + if (this.getSelectedConversation() != null) { if (this.getSelectedConversation().getMode() == Conversation.MODE_SINGLE) { menuArchiveMuc.setVisible(false); } else { menuArchiveChat.setVisible(false); } - if (this.getSelectedConversation().getNextEncryption() != Message.ENCRYPTION_NONE) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - menuSecure.setIcon(R.drawable.ic_lock_white_24dp); - } else { - menuSecure.setIcon(R.drawable.ic_action_secure); - } - } - if (this.getSelectedConversation().getMode() == Conversation.MODE_MULTI) { - menuAttach.setVisible(getSelectedConversation().getAccount().httpUploadAvailable() && getSelectedConversation().getMucOptions().participating()); - menuInviteContact.setVisible(getSelectedConversation().getMucOptions().canInvite()); - menuSecure.setVisible((Config.supportOpenPgp() || Config.supportOmemo()) && Config.multipleEncryptionChoices()); //only if pgp is supported we have a choice - } else { - menuSecure.setVisible(Config.multipleEncryptionChoices()); - } - if (this.getSelectedConversation().isMuted()) { - menuMute.setVisible(false); - } else { - menuUnmute.setVisible(false); - } - } - } - if (Config.supportOmemo()) { - new Handler().post(new Runnable() { - @Override - public void run() { - View view = findViewById(R.id.action_security); - if (view != null) { - view.setOnLongClickListener(new View.OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - return quickOmemoDebugger(getSelectedConversation()); - } - }); - } - } - }); - } - return super.onCreateOptionsMenu(menu); - } - - private boolean quickOmemoDebugger(Conversation c) { - if (c != null) { + if (this.getSelectedConversation().getNextEncryption() != Message.ENCRYPTION_NONE) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + menuSecure.setIcon(R.drawable.ic_lock_white_24dp); + } else { + menuSecure.setIcon(R.drawable.ic_action_secure); + } + } + if (this.getSelectedConversation().getMode() == Conversation.MODE_MULTI) { + menuAttach.setVisible(getSelectedConversation().getAccount().httpUploadAvailable() && getSelectedConversation().getMucOptions().participating()); + menuInviteContact.setVisible(getSelectedConversation().getMucOptions().canInvite()); + menuSecure.setVisible((Config.supportOpenPgp() || Config.supportOmemo()) && Config.multipleEncryptionChoices()); //only if pgp is supported we have a choice + } else { + menuSecure.setVisible(Config.multipleEncryptionChoices()); + } + if (this.getSelectedConversation().isMuted()) { + menuMute.setVisible(false); + } else { + menuUnmute.setVisible(false); + } + } + } + if (Config.supportOmemo()) { + new Handler().post(new Runnable() { + @Override + public void run() { + View view = findViewById(R.id.action_security); + if (view != null) { + view.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + return quickOmemoDebugger(getSelectedConversation()); + } + }); + } + } + }); + } + return super.onCreateOptionsMenu(menu); + } + + private boolean quickOmemoDebugger(Conversation c) { + if (c != null) { boolean single = c.getMode() == Conversation.MODE_SINGLE; - AxolotlService axolotlService = c.getAccount().getAxolotlService(); - Pair capabilityJidPair = axolotlService.isConversationAxolotlCapableDetailed(c); - switch (capabilityJidPair.first) { - case MISSING_PRESENCE: - Toast.makeText(ConversationActivity.this,single ? getString(R.string.missing_presence_subscription) : getString(R.string.missing_presence_subscription_with_x,capabilityJidPair.second.toBareJid().toString()),Toast.LENGTH_SHORT).show(); - return true; - case MISSING_KEYS: - Toast.makeText(ConversationActivity.this,single ? getString(R.string.missing_omemo_keys) : getString(R.string.missing_keys_from_x,capabilityJidPair.second.toBareJid().toString()),Toast.LENGTH_SHORT).show(); + AxolotlService axolotlService = c.getAccount().getAxolotlService(); + Pair capabilityJidPair = axolotlService.isConversationAxolotlCapableDetailed(c); + switch (capabilityJidPair.first) { + case MISSING_PRESENCE: + Toast.makeText(ConversationActivity.this, single ? getString(R.string.missing_presence_subscription) : getString(R.string.missing_presence_subscription_with_x, capabilityJidPair.second.toBareJid().toString()), Toast.LENGTH_SHORT).show(); return true; - case WRONG_CONFIGURATION: - Toast.makeText(ConversationActivity.this,R.string.wrong_conference_configuration, Toast.LENGTH_SHORT).show(); - return true; - case NO_MEMBERS: - Toast.makeText(ConversationActivity.this,R.string.this_conference_has_no_members, Toast.LENGTH_SHORT).show(); - return true; - } - } - return false; - } - - protected void selectPresenceToAttachFile(final int attachmentChoice, final int encryption) { - final Conversation conversation = getSelectedConversation(); - final Account account = conversation.getAccount(); - final OnPresenceSelected callback = new OnPresenceSelected() { - - @Override - public void onPresenceSelected() { - Intent intent = new Intent(); - boolean chooser = false; - String fallbackPackageId = null; - switch (attachmentChoice) { - case ATTACHMENT_CHOICE_CHOOSE_IMAGE: - intent.setAction(Intent.ACTION_GET_CONTENT); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); - } - intent.setType("image/*"); - chooser = true; - break; + case MISSING_KEYS: + Toast.makeText(ConversationActivity.this, single ? getString(R.string.missing_omemo_keys) : getString(R.string.missing_keys_from_x, capabilityJidPair.second.toBareJid().toString()), Toast.LENGTH_SHORT).show(); + return true; + case WRONG_CONFIGURATION: + Toast.makeText(ConversationActivity.this, R.string.wrong_conference_configuration, Toast.LENGTH_SHORT).show(); + return true; + case NO_MEMBERS: + Toast.makeText(ConversationActivity.this, R.string.this_conference_has_no_members, Toast.LENGTH_SHORT).show(); + return true; + } + } + return false; + } + + protected void selectPresenceToAttachFile(final int attachmentChoice, final int encryption) { + final Conversation conversation = getSelectedConversation(); + final Account account = conversation.getAccount(); + final OnPresenceSelected callback = new OnPresenceSelected() { + + @Override + public void onPresenceSelected() { + Intent intent = new Intent(); + boolean chooser = false; + String fallbackPackageId = null; + switch (attachmentChoice) { + case ATTACHMENT_CHOICE_CHOOSE_IMAGE: + intent.setAction(Intent.ACTION_GET_CONTENT); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); + } + intent.setType("image/*"); + chooser = true; + break; case ATTACHMENT_CHOICE_CHOOSE_VIDEO: intent.setAction(Intent.ACTION_GET_CONTENT); intent.setType("video/*"); chooser = true; break; - case ATTACHMENT_CHOICE_TAKE_PHOTO: - Uri uri = xmppConnectionService.getFileBackend().getTakePhotoUri(); - intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE); - intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); - mPendingPhotoUris.clear(); - mPendingPhotoUris.add(uri); - break; - case ATTACHMENT_CHOICE_CHOOSE_FILE: - chooser = true; - intent.setType("*/*"); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setAction(Intent.ACTION_GET_CONTENT); - break; + case ATTACHMENT_CHOICE_TAKE_PHOTO: + Uri uri = xmppConnectionService.getFileBackend().getTakePhotoUri(); + intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE); + intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); + mPendingPhotoUris.clear(); + mPendingPhotoUris.add(uri); + break; + case ATTACHMENT_CHOICE_CHOOSE_FILE: + chooser = true; + intent.setType("*/*"); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setAction(Intent.ACTION_GET_CONTENT); + break; case ATTACHMENT_CHOICE_RECORD_VOICE: - startActivityForResult(new Intent(getApplicationContext(), RecordingActivity.class),attachmentChoice); + startActivityForResult(new Intent(getApplicationContext(), RecordingActivity.class), attachmentChoice); break; case ATTACHMENT_CHOICE_LOCATION: - startActivityForResult(new Intent(getApplicationContext(), ShareLocationActivity.class),attachmentChoice); - break; - } - if (intent.resolveActivity(getPackageManager()) != null) { + startActivityForResult(new Intent(getApplicationContext(), ShareLocationActivity.class), attachmentChoice); + break; + } + if (intent.resolveActivity(getPackageManager()) != null) { Log.d(Config.LOGTAG, "Attachment: " + attachmentChoice); - if (chooser) { - startActivityForResult( - Intent.createChooser(intent, getString(R.string.perform_action_with)), - attachmentChoice); - } else { - startActivityForResult(intent, attachmentChoice); - } - } else if (fallbackPackageId != null) { - startActivity(getInstallApkIntent(fallbackPackageId)); - } - } - }; - if ((account.httpUploadAvailable() || attachmentChoice == ATTACHMENT_CHOICE_LOCATION) && encryption != Message.ENCRYPTION_OTR) { - conversation.setNextCounterpart(null); - callback.onPresenceSelected(); - } else { - selectPresence(conversation, callback); - } - } - - private Intent getInstallApkIntent(final String packageId) { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse("market://details?id=" + packageId)); - if (intent.resolveActivity(getPackageManager()) != null) { - return intent; - } else { - intent.setData(Uri.parse("http://play.google.com/store/apps/details?id=" + packageId)); - return intent; - } - } - - public void attachFile(final int attachmentChoice) { - if (attachmentChoice != ATTACHMENT_CHOICE_LOCATION) { - if (!hasStoragePermission(attachmentChoice)) { - return; - } - } + if (chooser) { + startActivityForResult( + Intent.createChooser(intent, getString(R.string.perform_action_with)), + attachmentChoice); + } else { + startActivityForResult(intent, attachmentChoice); + } + } else if (fallbackPackageId != null) { + startActivity(getInstallApkIntent(fallbackPackageId)); + } + } + }; + if ((account.httpUploadAvailable() || attachmentChoice == ATTACHMENT_CHOICE_LOCATION) && encryption != Message.ENCRYPTION_OTR) { + conversation.setNextCounterpart(null); + callback.onPresenceSelected(); + } else { + selectPresence(conversation, callback); + } + } + + private Intent getInstallApkIntent(final String packageId) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse("market://details?id=" + packageId)); + if (intent.resolveActivity(getPackageManager()) != null) { + return intent; + } else { + intent.setData(Uri.parse("http://play.google.com/store/apps/details?id=" + packageId)); + return intent; + } + } + + public void attachFile(final int attachmentChoice) { + if (attachmentChoice != ATTACHMENT_CHOICE_LOCATION) { + if (!hasStoragePermission(attachmentChoice)) { + return; + } + } if (attachmentChoice == ATTACHMENT_CHOICE_RECORD_VOICE) { if (!hasMicPermission(attachmentChoice)) { return; @@ -748,128 +748,128 @@ public class ConversationActivity extends XmppActivity } } switch (attachmentChoice) { - case ATTACHMENT_CHOICE_LOCATION: - getPreferences().edit().putString("recently_used_quick_action", "location").apply(); - break; - case ATTACHMENT_CHOICE_RECORD_VOICE: - getPreferences().edit().putString("recently_used_quick_action", "voice").apply(); - break; - case ATTACHMENT_CHOICE_TAKE_PHOTO: - getPreferences().edit().putString("recently_used_quick_action", "photo").apply(); - break; - case ATTACHMENT_CHOICE_CHOOSE_IMAGE: - getPreferences().edit().putString("recently_used_quick_action", "picture").apply(); - break; - } - final Conversation conversation = getSelectedConversation(); - final int encryption = conversation.getNextEncryption(); - final int mode = conversation.getMode(); - if (encryption == Message.ENCRYPTION_PGP) { - if (hasPgp()) { - if (mode == Conversation.MODE_SINGLE && conversation.getContact().getPgpKeyId() != 0) { - xmppConnectionService.getPgpEngine().hasKey( - conversation.getContact(), - new UiCallback() { - - @Override - public void userInputRequried(PendingIntent pi, Contact contact) { - ConversationActivity.this.runIntent(pi, attachmentChoice); - } - - @Override - public void success(Contact contact) { - selectPresenceToAttachFile(attachmentChoice, encryption); - } - - @Override - public void error(int error, Contact contact) { - replaceToast(getString(error)); - } - }); - } else if (mode == Conversation.MODE_MULTI && conversation.getMucOptions().pgpKeysInUse()) { - if (!conversation.getMucOptions().everybodyHasKeys()) { - Toast warning = Toast - .makeText(this, - R.string.missing_public_keys, - Toast.LENGTH_LONG); - warning.setGravity(Gravity.CENTER_VERTICAL, 0, 0); - warning.show(); - } - selectPresenceToAttachFile(attachmentChoice, encryption); - } else { - final ConversationFragment fragment = (ConversationFragment) getFragmentManager() - .findFragmentByTag("conversation"); - if (fragment != null) { - fragment.showNoPGPKeyDialog(false, - new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, - int which) { + case ATTACHMENT_CHOICE_LOCATION: + getPreferences().edit().putString("recently_used_quick_action", "location").apply(); + break; + case ATTACHMENT_CHOICE_RECORD_VOICE: + getPreferences().edit().putString("recently_used_quick_action", "voice").apply(); + break; + case ATTACHMENT_CHOICE_TAKE_PHOTO: + getPreferences().edit().putString("recently_used_quick_action", "photo").apply(); + break; + case ATTACHMENT_CHOICE_CHOOSE_IMAGE: + getPreferences().edit().putString("recently_used_quick_action", "picture").apply(); + break; + } + final Conversation conversation = getSelectedConversation(); + final int encryption = conversation.getNextEncryption(); + final int mode = conversation.getMode(); + if (encryption == Message.ENCRYPTION_PGP) { + if (hasPgp()) { + if (mode == Conversation.MODE_SINGLE && conversation.getContact().getPgpKeyId() != 0) { + xmppConnectionService.getPgpEngine().hasKey( + conversation.getContact(), + new UiCallback() { + + @Override + public void userInputRequried(PendingIntent pi, Contact contact) { + ConversationActivity.this.runIntent(pi, attachmentChoice); + } + + @Override + public void success(Contact contact) { + selectPresenceToAttachFile(attachmentChoice, encryption); + } + + @Override + public void error(int error, Contact contact) { + replaceToast(getString(error)); + } + }); + } else if (mode == Conversation.MODE_MULTI && conversation.getMucOptions().pgpKeysInUse()) { + if (!conversation.getMucOptions().everybodyHasKeys()) { + Toast warning = Toast + .makeText(this, + R.string.missing_public_keys, + Toast.LENGTH_LONG); + warning.setGravity(Gravity.CENTER_VERTICAL, 0, 0); + warning.show(); + } + selectPresenceToAttachFile(attachmentChoice, encryption); + } else { + final ConversationFragment fragment = (ConversationFragment) getFragmentManager() + .findFragmentByTag("conversation"); + if (fragment != null) { + fragment.showNoPGPKeyDialog(false, + new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, + int which) { conversation.setNextEncryption(Message.ENCRYPTION_NONE); xmppConnectionService.updateConversation(conversation); selectPresenceToAttachFile(attachmentChoice, Message.ENCRYPTION_NONE); - } - }); - } - } - } else { - showInstallPgpDialog(); - } - } else { - if (encryption != Message.ENCRYPTION_AXOLOTL || !trustKeysIfNeeded(REQUEST_TRUST_KEYS_MENU, attachmentChoice)) { - selectPresenceToAttachFile(attachmentChoice, encryption); - } - } - } - - @Override - public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { - if (grantResults.length > 0) - if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { - if (requestCode == REQUEST_START_DOWNLOAD) { - if (this.mPendingDownloadableMessage != null) { - startDownloadable(this.mPendingDownloadableMessage); - } - } else { - attachFile(requestCode); - } - } else { - Toast.makeText(this, R.string.no_permission, Toast.LENGTH_SHORT).show(); - } - } - - public void startDownloadable(Message message) { - if (!hasStoragePermission(ConversationActivity.REQUEST_START_DOWNLOAD)) { - this.mPendingDownloadableMessage = message; - return; - } - Transferable transferable = message.getTransferable(); - if (transferable != null) { - if (!transferable.start()) { - Toast.makeText(this, R.string.not_connected_try_again, Toast.LENGTH_SHORT).show(); - } - } else if (message.treatAsDownloadable() != Message.Decision.NEVER) { - xmppConnectionService.getHttpConnectionManager().createNewDownloadConnection(message, true); - } - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - if (item.getItemId() == android.R.id.home) { - showConversationsOverview(); - return true; - } else if (item.getItemId() == R.id.action_add) { - startActivity(new Intent(this, StartConversationActivity.class)); - return true; - } else if (getSelectedConversation() != null) { - switch (item.getItemId()) { - case R.id.action_attach_file: - attachFileDialog(); - break; - case R.id.action_archive_chat: - this.endConversation(getSelectedConversation()); - break; + } + }); + } + } + } else { + showInstallPgpDialog(); + } + } else { + if (encryption != Message.ENCRYPTION_AXOLOTL || !trustKeysIfNeeded(REQUEST_TRUST_KEYS_MENU, attachmentChoice)) { + selectPresenceToAttachFile(attachmentChoice, encryption); + } + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { + if (grantResults.length > 0) + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + if (requestCode == REQUEST_START_DOWNLOAD) { + if (this.mPendingDownloadableMessage != null) { + startDownloadable(this.mPendingDownloadableMessage); + } + } else { + attachFile(requestCode); + } + } else { + Toast.makeText(this, R.string.no_permission, Toast.LENGTH_SHORT).show(); + } + } + + public void startDownloadable(Message message) { + if (!hasStoragePermission(ConversationActivity.REQUEST_START_DOWNLOAD)) { + this.mPendingDownloadableMessage = message; + return; + } + Transferable transferable = message.getTransferable(); + if (transferable != null) { + if (!transferable.start()) { + Toast.makeText(this, R.string.not_connected_try_again, Toast.LENGTH_SHORT).show(); + } + } else if (message.treatAsDownloadable() != Message.Decision.NEVER) { + xmppConnectionService.getHttpConnectionManager().createNewDownloadConnection(message, true); + } + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + if (item.getItemId() == android.R.id.home) { + showConversationsOverview(); + return true; + } else if (item.getItemId() == R.id.action_add) { + startActivity(new Intent(this, StartConversationActivity.class)); + return true; + } else if (getSelectedConversation() != null) { + switch (item.getItemId()) { + case R.id.action_attach_file: + attachFileDialog(); + break; + case R.id.action_archive_chat: + this.endConversation(getSelectedConversation()); + break; case R.id.action_archive_muc: AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(getString(R.string.action_end_conversation_muc)); @@ -884,80 +884,80 @@ public class ConversationActivity extends XmppActivity }); builder.create().show(); break; - case R.id.action_invite: - inviteToConversation(getSelectedConversation()); - break; - case R.id.action_security: - selectEncryptionDialog(getSelectedConversation()); - break; - case R.id.action_clear_history: - clearHistoryDialog(getSelectedConversation()); - break; - case R.id.action_mute: - muteConversationDialog(getSelectedConversation()); - break; - case R.id.action_unmute: - unmuteConversation(getSelectedConversation()); - break; - case R.id.action_block: - BlockContactDialog.show(this, xmppConnectionService, getSelectedConversation()); - break; - case R.id.action_unblock: - BlockContactDialog.show(this, xmppConnectionService, getSelectedConversation()); - break; - default: - break; - } - return super.onOptionsItemSelected(item); - } else { - return super.onOptionsItemSelected(item); - } - } - - public void endConversation(Conversation conversation) { - endConversation(conversation, true, true); - } - - public void endConversation(Conversation conversation, boolean showOverview, boolean reinit) { - if (showOverview) { - showConversationsOverview(); - } - xmppConnectionService.archiveConversation(conversation); - if (reinit) { - if (conversationList.size() > 0) { - setSelectedConversation(conversationList.get(0)); - this.mConversationFragment.reInit(getSelectedConversation()); - } else { - setSelectedConversation(null); - if (mRedirected.compareAndSet(false, true)) { - Intent intent = new Intent(this, StartConversationActivity.class); - intent.putExtra("init", true); - startActivity(intent); - finish(); - } - } - } - } - - @SuppressLint("InflateParams") - protected void clearHistoryDialog(final Conversation conversation) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(getString(R.string.clear_conversation_history)); - View dialogView = getLayoutInflater().inflate( - R.layout.dialog_clear_history, null); + case R.id.action_invite: + inviteToConversation(getSelectedConversation()); + break; + case R.id.action_security: + selectEncryptionDialog(getSelectedConversation()); + break; + case R.id.action_clear_history: + clearHistoryDialog(getSelectedConversation()); + break; + case R.id.action_mute: + muteConversationDialog(getSelectedConversation()); + break; + case R.id.action_unmute: + unmuteConversation(getSelectedConversation()); + break; + case R.id.action_block: + BlockContactDialog.show(this, xmppConnectionService, getSelectedConversation()); + break; + case R.id.action_unblock: + BlockContactDialog.show(this, xmppConnectionService, getSelectedConversation()); + break; + default: + break; + } + return super.onOptionsItemSelected(item); + } else { + return super.onOptionsItemSelected(item); + } + } + + public void endConversation(Conversation conversation) { + endConversation(conversation, true, true); + } + + public void endConversation(Conversation conversation, boolean showOverview, boolean reinit) { + if (showOverview) { + showConversationsOverview(); + } + xmppConnectionService.archiveConversation(conversation); + if (reinit) { + if (conversationList.size() > 0) { + setSelectedConversation(conversationList.get(0)); + this.mConversationFragment.reInit(getSelectedConversation()); + } else { + setSelectedConversation(null); + if (mRedirected.compareAndSet(false, true)) { + Intent intent = new Intent(this, StartConversationActivity.class); + intent.putExtra("init", true); + startActivity(intent); + finish(); + } + } + } + } + + @SuppressLint("InflateParams") + protected void clearHistoryDialog(final Conversation conversation) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(getString(R.string.clear_conversation_history)); + View dialogView = getLayoutInflater().inflate( + R.layout.dialog_clear_history, null); final CheckBox endConversationCheckBox = (CheckBox) dialogView .findViewById(R.id.end_conversation_checkbox); if (conversation.getMode() == Conversation.MODE_SINGLE) { endConversationCheckBox.setVisibility(View.VISIBLE); } - builder.setView(dialogView); - builder.setNegativeButton(getString(R.string.cancel), null); - builder.setPositiveButton(getString(R.string.delete_messages), - new OnClickListener() { + 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); + @Override + public void onClick(DialogInterface dialog, int which) { + ConversationActivity.this.xmppConnectionService.clearConversationHistory(conversation); if (conversation.getMode() == Conversation.MODE_SINGLE) { if (endConversationCheckBox.isChecked()) { endConversation(conversation); @@ -969,436 +969,436 @@ public class ConversationActivity extends XmppActivity updateConversationList(); ConversationActivity.this.mConversationFragment.updateMessages(); } - } - }); - builder.create().show(); - } - - protected void attachFileDialog() { - View menuAttachFile = findViewById(R.id.action_attach_file); - if (menuAttachFile == null) { - return; - } - PopupMenu attachFilePopup = new PopupMenu(this, menuAttachFile); - attachFilePopup.inflate(R.menu.attachment_choices); - if (new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION).resolveActivity(getPackageManager()) == null) { - attachFilePopup.getMenu().findItem(R.id.attach_record_voice).setVisible(false); - } - if (new Intent("de.pixart.messenger.location.request").resolveActivity(getPackageManager()) == null) { - attachFilePopup.getMenu().findItem(R.id.attach_location).setVisible(false); - } - attachFilePopup.setOnMenuItemClickListener(new OnMenuItemClickListener() { - - @Override - public boolean onMenuItemClick(MenuItem item) { - switch (item.getItemId()) { - case R.id.attach_choose_picture: - attachFile(ATTACHMENT_CHOICE_CHOOSE_IMAGE); - break; - case R.id.attach_take_picture: - attachFile(ATTACHMENT_CHOICE_TAKE_PHOTO); - break; + } + }); + builder.create().show(); + } + + protected void attachFileDialog() { + View menuAttachFile = findViewById(R.id.action_attach_file); + if (menuAttachFile == null) { + return; + } + PopupMenu attachFilePopup = new PopupMenu(this, menuAttachFile); + attachFilePopup.inflate(R.menu.attachment_choices); + if (new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION).resolveActivity(getPackageManager()) == null) { + attachFilePopup.getMenu().findItem(R.id.attach_record_voice).setVisible(false); + } + if (new Intent("de.pixart.messenger.location.request").resolveActivity(getPackageManager()) == null) { + attachFilePopup.getMenu().findItem(R.id.attach_location).setVisible(false); + } + attachFilePopup.setOnMenuItemClickListener(new OnMenuItemClickListener() { + + @Override + public boolean onMenuItemClick(MenuItem item) { + switch (item.getItemId()) { + case R.id.attach_choose_picture: + attachFile(ATTACHMENT_CHOICE_CHOOSE_IMAGE); + break; + case R.id.attach_take_picture: + attachFile(ATTACHMENT_CHOICE_TAKE_PHOTO); + break; case R.id.attach_choose_video: attachFile(ATTACHMENT_CHOICE_CHOOSE_VIDEO); break; - case R.id.attach_choose_file: - attachFile(ATTACHMENT_CHOICE_CHOOSE_FILE); - break; - case R.id.attach_record_voice: - attachFile(ATTACHMENT_CHOICE_RECORD_VOICE); - break; - case R.id.attach_location: - attachFile(ATTACHMENT_CHOICE_LOCATION); - break; - } - return false; - } - }); - attachFilePopup.show(); - } - - public void verifyOtrSessionDialog(final Conversation conversation, View view) { - if (!conversation.hasValidOtrSession() || conversation.getOtrSession().getSessionStatus() != SessionStatus.ENCRYPTED) { - Toast.makeText(this, R.string.otr_session_not_started, Toast.LENGTH_LONG).show(); - return; - } - if (view == null) { - return; - } - PopupMenu popup = new PopupMenu(this, view); - popup.inflate(R.menu.verification_choices); - popup.setOnMenuItemClickListener(new OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem menuItem) { - Intent intent = new Intent(ConversationActivity.this, VerifyOTRActivity.class); - intent.setAction(VerifyOTRActivity.ACTION_VERIFY_CONTACT); - intent.putExtra("contact", conversation.getContact().getJid().toBareJid().toString()); - intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().toBareJid().toString()); - switch (menuItem.getItemId()) { - case R.id.scan_fingerprint: - intent.putExtra("mode", VerifyOTRActivity.MODE_SCAN_FINGERPRINT); - break; - case R.id.ask_question: - intent.putExtra("mode", VerifyOTRActivity.MODE_ASK_QUESTION); - break; - case R.id.manual_verification: - intent.putExtra("mode", VerifyOTRActivity.MODE_MANUAL_VERIFICATION); - break; - } - startActivity(intent); - return true; - } - }); - popup.show(); - } - - protected void selectEncryptionDialog(final Conversation conversation) { - View menuItemView = findViewById(R.id.action_security); - if (menuItemView == null) { - return; - } - PopupMenu popup = new PopupMenu(this, menuItemView); - final ConversationFragment fragment = (ConversationFragment) getFragmentManager() - .findFragmentByTag("conversation"); - if (fragment != null) { - popup.setOnMenuItemClickListener(new OnMenuItemClickListener() { - - @Override - public boolean onMenuItemClick(MenuItem item) { - switch (item.getItemId()) { - case R.id.encryption_choice_none: - conversation.setNextEncryption(Message.ENCRYPTION_NONE); - item.setChecked(true); - break; - case R.id.encryption_choice_otr: - conversation.setNextEncryption(Message.ENCRYPTION_OTR); - item.setChecked(true); - break; - case R.id.encryption_choice_pgp: - if (hasPgp()) { - if (conversation.getAccount().getPgpSignature() != null) { - conversation.setNextEncryption(Message.ENCRYPTION_PGP); - item.setChecked(true); - } else { - announcePgp(conversation.getAccount(), conversation, onOpenPGPKeyPublished); - } - } else { - showInstallPgpDialog(); - } - break; - case R.id.encryption_choice_axolotl: - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(conversation.getAccount()) - + "Enabled axolotl for Contact " + conversation.getContact().getJid()); - conversation.setNextEncryption(Message.ENCRYPTION_AXOLOTL); - item.setChecked(true); - break; - default: - conversation.setNextEncryption(Message.ENCRYPTION_NONE); - break; - } - xmppConnectionService.updateConversation(conversation); - fragment.updateChatMsgHint(); - invalidateOptionsMenu(); - refreshUi(); - return true; - } - }); - popup.inflate(R.menu.encryption_choices); - MenuItem otr = popup.getMenu().findItem(R.id.encryption_choice_otr); - MenuItem none = popup.getMenu().findItem(R.id.encryption_choice_none); - MenuItem pgp = popup.getMenu().findItem(R.id.encryption_choice_pgp); - MenuItem axolotl = popup.getMenu().findItem(R.id.encryption_choice_axolotl); - pgp.setVisible(Config.supportOpenPgp()); - none.setVisible(Config.supportUnencrypted() || conversation.getMode() == Conversation.MODE_MULTI); - otr.setVisible(Config.supportOtr()); - axolotl.setVisible(Config.supportOmemo()); - if (conversation.getMode() == Conversation.MODE_MULTI) { - otr.setVisible(false); - } - if (!conversation.getAccount().getAxolotlService().isConversationAxolotlCapable(conversation)) { - axolotl.setEnabled(false); - } - switch (conversation.getNextEncryption()) { - case Message.ENCRYPTION_NONE: - none.setChecked(true); - break; - case Message.ENCRYPTION_OTR: - otr.setChecked(true); - break; - case Message.ENCRYPTION_PGP: - pgp.setChecked(true); - break; - case Message.ENCRYPTION_AXOLOTL: - axolotl.setChecked(true); - break; - default: - none.setChecked(true); - break; - } - popup.show(); - } - } - - protected void muteConversationDialog(final Conversation conversation) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - 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.updateConversation(conversation); - updateConversationList(); - ConversationActivity.this.mConversationFragment.updateMessages(); - invalidateOptionsMenu(); - } - }); - builder.create().show(); - } - - public void unmuteConversation(final Conversation conversation) { - conversation.setMutedTill(0); - this.xmppConnectionService.updateConversation(conversation); - updateConversationList(); - ConversationActivity.this.mConversationFragment.updateMessages(); - invalidateOptionsMenu(); - } - - @Override - public void onBackPressed() { - if (!isConversationsOverviewVisable()) { - showConversationsOverview(); - } else { - moveTaskToBack(true); - } - } - - @Override - public boolean onKeyUp(int key, KeyEvent event) { - int rotation = getWindowManager().getDefaultDisplay().getRotation(); - final int upKey; - final int downKey; - switch (rotation) { - case Surface.ROTATION_90: - upKey = KeyEvent.KEYCODE_DPAD_LEFT; - downKey = KeyEvent.KEYCODE_DPAD_RIGHT; - break; - case Surface.ROTATION_180: - upKey = KeyEvent.KEYCODE_DPAD_DOWN; - downKey = KeyEvent.KEYCODE_DPAD_UP; - break; - case Surface.ROTATION_270: - upKey = KeyEvent.KEYCODE_DPAD_RIGHT; - downKey = KeyEvent.KEYCODE_DPAD_LEFT; - break; - default: - upKey = KeyEvent.KEYCODE_DPAD_UP; - downKey = KeyEvent.KEYCODE_DPAD_DOWN; - } - final boolean modifier = event.isCtrlPressed() || (event.getMetaState() & KeyEvent.META_ALT_LEFT_ON) != 0; - if (modifier && key == KeyEvent.KEYCODE_TAB && isConversationsOverviewHideable()) { - toggleConversationsOverview(); - return true; - } else if (modifier && key == KeyEvent.KEYCODE_SPACE) { - startActivity(new Intent(this, StartConversationActivity.class)); - return true; - } else if (modifier && key == downKey) { - if (isConversationsOverviewHideable() && !isConversationsOverviewVisable()) { - showConversationsOverview(); - } - return selectDownConversation(); - } else if (modifier && key == upKey) { - if (isConversationsOverviewHideable() && !isConversationsOverviewVisable()) { - showConversationsOverview(); - } - return selectUpConversation(); - } else if (modifier && key == KeyEvent.KEYCODE_1) { - return openConversationByIndex(0); - } else if (modifier && key == KeyEvent.KEYCODE_2) { - return openConversationByIndex(1); - } else if (modifier && key == KeyEvent.KEYCODE_3) { - return openConversationByIndex(2); - } else if (modifier && key == KeyEvent.KEYCODE_4) { - return openConversationByIndex(3); - } else if (modifier && key == KeyEvent.KEYCODE_5) { - return openConversationByIndex(4); - } else if (modifier && key == KeyEvent.KEYCODE_6) { - return openConversationByIndex(5); - } else if (modifier && key == KeyEvent.KEYCODE_7) { - return openConversationByIndex(6); - } else if (modifier && key == KeyEvent.KEYCODE_8) { - return openConversationByIndex(7); - } else if (modifier && key == KeyEvent.KEYCODE_9) { - return openConversationByIndex(8); - } else if (modifier && key == KeyEvent.KEYCODE_0) { - return openConversationByIndex(9); - } else { - return super.onKeyUp(key, event); - } - } - - private void toggleConversationsOverview() { - if (isConversationsOverviewVisable()) { - hideConversationsOverview(); - if (mConversationFragment != null) { - mConversationFragment.setFocusOnInputField(); - } - } else { - showConversationsOverview(); - } - } - - private boolean selectUpConversation() { - if (this.mSelectedConversation != null) { - int index = this.conversationList.indexOf(this.mSelectedConversation); - if (index > 0) { - return openConversationByIndex(index - 1); - } - } - return false; - } - - private boolean selectDownConversation() { - if (this.mSelectedConversation != null) { - int index = this.conversationList.indexOf(this.mSelectedConversation); - if (index != -1 && index < this.conversationList.size() - 1) { - return openConversationByIndex(index + 1); - } - } - return false; - } - - private boolean openConversationByIndex(int index) { - try { - this.conversationWasSelectedByKeyboard = true; - setSelectedConversation(this.conversationList.get(index)); - this.mConversationFragment.reInit(getSelectedConversation()); - if (index > listView.getLastVisiblePosition() - 1 || index < listView.getFirstVisiblePosition() + 1) { - this.listView.setSelection(index); - } - openConversation(); - return true; - } catch (IndexOutOfBoundsException e) { - return false; - } - } - - @Override - protected void onNewIntent(final Intent intent) { - if (intent != null && ACTION_VIEW_CONVERSATION.equals(intent.getAction())) { - mOpenConversation = null; - mUnprocessedNewIntent = true; - if (xmppConnectionServiceBound) { - handleViewConversationIntent(intent); - intent.setAction(Intent.ACTION_MAIN); - } else { - setIntent(intent); - } - } - } - - @Override - public void onStart() { - super.onStart(); - this.mRedirected.set(false); - if (this.xmppConnectionServiceBound) { - this.onBackendConnected(); - } - if (conversationList.size() >= 1) { - this.onConversationUpdate(); - } - } - - @Override - public void onPause() { - listView.discardUndo(); - super.onPause(); - this.mActivityPaused = true; - } - - @Override - public void onResume() { - super.onResume(); - final int theme = findTheme(); - final boolean usingEnterKey = usingEnterKey(); - if (this.mTheme != theme || usingEnterKey != mUsingEnterKey) { - recreate(); - } - this.mActivityPaused = false; - - if (!isConversationsOverviewVisable() || !isConversationsOverviewHideable()) { - sendReadMarkerIfNecessary(getSelectedConversation()); - } - } - - @Override - public void onSaveInstanceState(final Bundle savedInstanceState) { - Conversation conversation = getSelectedConversation(); - if (conversation != null) { - savedInstanceState.putString(STATE_OPEN_CONVERSATION, conversation.getUuid()); - Pair scrollPosition = mConversationFragment.getScrollPosition(); - if (scrollPosition != null) { - savedInstanceState.putInt(STATE_FIRST_VISIBLE, scrollPosition.first); - savedInstanceState.putInt(STATE_OFFSET_FROM_TOP, scrollPosition.second); + case R.id.attach_choose_file: + attachFile(ATTACHMENT_CHOICE_CHOOSE_FILE); + break; + case R.id.attach_record_voice: + attachFile(ATTACHMENT_CHOICE_RECORD_VOICE); + break; + case R.id.attach_location: + attachFile(ATTACHMENT_CHOICE_LOCATION); + break; + } + return false; } - } else { - savedInstanceState.remove(STATE_OPEN_CONVERSATION); - } - savedInstanceState.putBoolean(STATE_PANEL_OPEN, isConversationsOverviewVisable()); - if (this.mPendingImageUris.size() >= 1) { - Log.d(Config.LOGTAG,"ConversationActivity.onSaveInstanceState() - saving pending image uri"); - savedInstanceState.putString(STATE_PENDING_IMAGE_URI, this.mPendingImageUris.get(0).toString()); - } else if (this.mPendingPhotoUris.size() >= 1) { - savedInstanceState.putString(STATE_PENDING_PHOTO_URI, this.mPendingPhotoUris.get(0).toString()); - } else { - savedInstanceState.remove(STATE_PENDING_IMAGE_URI); - savedInstanceState.remove(STATE_PENDING_PHOTO_URI); - } - super.onSaveInstanceState(savedInstanceState); - } - - private void clearPending() { - mPendingImageUris.clear(); - mPendingFileUris.clear(); - mPendingVideoUris.clear(); - mPendingPhotoUris.clear(); - mPendingGeoUri = null; - mPostponedActivityResult = null; - } - - @Override - void onBackendConnected() { - this.xmppConnectionService.getNotificationService().setIsInForeground(true); - updateConversationList(); + }); + attachFilePopup.show(); + } - Bundle extras = getIntent().getExtras(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (extras != null && extras.containsKey("FirstStart")) { - FirstStartTime = extras.getLong("FirstStart"); - Log.d(Config.LOGTAG, "Get first start time from StartUI: " + FirstStartTime); - } - } else { - FirstStartTime = System.currentTimeMillis(); - Log.d(Config.LOGTAG, "Device is running Android < SDK 23, no restart required: " + FirstStartTime); + public void verifyOtrSessionDialog(final Conversation conversation, View view) { + if (!conversation.hasValidOtrSession() || conversation.getOtrSession().getSessionStatus() != SessionStatus.ENCRYPTED) { + Toast.makeText(this, R.string.otr_session_not_started, Toast.LENGTH_LONG).show(); + return; } + if (view == null) { + return; + } + PopupMenu popup = new PopupMenu(this, view); + popup.inflate(R.menu.verification_choices); + popup.setOnMenuItemClickListener(new OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + Intent intent = new Intent(ConversationActivity.this, VerifyOTRActivity.class); + intent.setAction(VerifyOTRActivity.ACTION_VERIFY_CONTACT); + intent.putExtra("contact", conversation.getContact().getJid().toBareJid().toString()); + intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().toBareJid().toString()); + switch (menuItem.getItemId()) { + case R.id.scan_fingerprint: + intent.putExtra("mode", VerifyOTRActivity.MODE_SCAN_FINGERPRINT); + break; + case R.id.ask_question: + intent.putExtra("mode", VerifyOTRActivity.MODE_ASK_QUESTION); + break; + case R.id.manual_verification: + intent.putExtra("mode", VerifyOTRActivity.MODE_MANUAL_VERIFICATION); + break; + } + startActivity(intent); + return true; + } + }); + popup.show(); + } - if (mPendingConferenceInvite != null) { - if (mPendingConferenceInvite.execute(this)) { - mToast = Toast.makeText(this, R.string.creating_conference, Toast.LENGTH_LONG); - mToast.show(); - } - mPendingConferenceInvite = null; - } - - if (FirstStartTime == 0) { + protected void selectEncryptionDialog(final Conversation conversation) { + View menuItemView = findViewById(R.id.action_security); + if (menuItemView == null) { + return; + } + PopupMenu popup = new PopupMenu(this, menuItemView); + final ConversationFragment fragment = (ConversationFragment) getFragmentManager() + .findFragmentByTag("conversation"); + if (fragment != null) { + popup.setOnMenuItemClickListener(new OnMenuItemClickListener() { + + @Override + public boolean onMenuItemClick(MenuItem item) { + switch (item.getItemId()) { + case R.id.encryption_choice_none: + conversation.setNextEncryption(Message.ENCRYPTION_NONE); + item.setChecked(true); + break; + case R.id.encryption_choice_otr: + conversation.setNextEncryption(Message.ENCRYPTION_OTR); + item.setChecked(true); + break; + case R.id.encryption_choice_pgp: + if (hasPgp()) { + if (conversation.getAccount().getPgpSignature() != null) { + conversation.setNextEncryption(Message.ENCRYPTION_PGP); + item.setChecked(true); + } else { + announcePgp(conversation.getAccount(), conversation, onOpenPGPKeyPublished); + } + } else { + showInstallPgpDialog(); + } + break; + case R.id.encryption_choice_axolotl: + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(conversation.getAccount()) + + "Enabled axolotl for Contact " + conversation.getContact().getJid()); + conversation.setNextEncryption(Message.ENCRYPTION_AXOLOTL); + item.setChecked(true); + break; + default: + conversation.setNextEncryption(Message.ENCRYPTION_NONE); + break; + } + xmppConnectionService.updateConversation(conversation); + fragment.updateChatMsgHint(); + invalidateOptionsMenu(); + refreshUi(); + return true; + } + }); + popup.inflate(R.menu.encryption_choices); + MenuItem otr = popup.getMenu().findItem(R.id.encryption_choice_otr); + MenuItem none = popup.getMenu().findItem(R.id.encryption_choice_none); + MenuItem pgp = popup.getMenu().findItem(R.id.encryption_choice_pgp); + MenuItem axolotl = popup.getMenu().findItem(R.id.encryption_choice_axolotl); + pgp.setVisible(Config.supportOpenPgp()); + none.setVisible(Config.supportUnencrypted() || conversation.getMode() == Conversation.MODE_MULTI); + otr.setVisible(Config.supportOtr()); + axolotl.setVisible(Config.supportOmemo()); + if (conversation.getMode() == Conversation.MODE_MULTI) { + otr.setVisible(false); + } + if (!conversation.getAccount().getAxolotlService().isConversationAxolotlCapable(conversation)) { + axolotl.setEnabled(false); + } + switch (conversation.getNextEncryption()) { + case Message.ENCRYPTION_NONE: + none.setChecked(true); + break; + case Message.ENCRYPTION_OTR: + otr.setChecked(true); + break; + case Message.ENCRYPTION_PGP: + pgp.setChecked(true); + break; + case Message.ENCRYPTION_AXOLOTL: + axolotl.setChecked(true); + break; + default: + none.setChecked(true); + break; + } + popup.show(); + } + } + + protected void muteConversationDialog(final Conversation conversation) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + 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.updateConversation(conversation); + updateConversationList(); + ConversationActivity.this.mConversationFragment.updateMessages(); + invalidateOptionsMenu(); + } + }); + builder.create().show(); + } + + public void unmuteConversation(final Conversation conversation) { + conversation.setMutedTill(0); + this.xmppConnectionService.updateConversation(conversation); + updateConversationList(); + ConversationActivity.this.mConversationFragment.updateMessages(); + invalidateOptionsMenu(); + } + + @Override + public void onBackPressed() { + if (!isConversationsOverviewVisable()) { + showConversationsOverview(); + } else { + moveTaskToBack(true); + } + } + + @Override + public boolean onKeyUp(int key, KeyEvent event) { + int rotation = getWindowManager().getDefaultDisplay().getRotation(); + final int upKey; + final int downKey; + switch (rotation) { + case Surface.ROTATION_90: + upKey = KeyEvent.KEYCODE_DPAD_LEFT; + downKey = KeyEvent.KEYCODE_DPAD_RIGHT; + break; + case Surface.ROTATION_180: + upKey = KeyEvent.KEYCODE_DPAD_DOWN; + downKey = KeyEvent.KEYCODE_DPAD_UP; + break; + case Surface.ROTATION_270: + upKey = KeyEvent.KEYCODE_DPAD_RIGHT; + downKey = KeyEvent.KEYCODE_DPAD_LEFT; + break; + default: + upKey = KeyEvent.KEYCODE_DPAD_UP; + downKey = KeyEvent.KEYCODE_DPAD_DOWN; + } + final boolean modifier = event.isCtrlPressed() || (event.getMetaState() & KeyEvent.META_ALT_LEFT_ON) != 0; + if (modifier && key == KeyEvent.KEYCODE_TAB && isConversationsOverviewHideable()) { + toggleConversationsOverview(); + return true; + } else if (modifier && key == KeyEvent.KEYCODE_SPACE) { + startActivity(new Intent(this, StartConversationActivity.class)); + return true; + } else if (modifier && key == downKey) { + if (isConversationsOverviewHideable() && !isConversationsOverviewVisable()) { + showConversationsOverview(); + } + return selectDownConversation(); + } else if (modifier && key == upKey) { + if (isConversationsOverviewHideable() && !isConversationsOverviewVisable()) { + showConversationsOverview(); + } + return selectUpConversation(); + } else if (modifier && key == KeyEvent.KEYCODE_1) { + return openConversationByIndex(0); + } else if (modifier && key == KeyEvent.KEYCODE_2) { + return openConversationByIndex(1); + } else if (modifier && key == KeyEvent.KEYCODE_3) { + return openConversationByIndex(2); + } else if (modifier && key == KeyEvent.KEYCODE_4) { + return openConversationByIndex(3); + } else if (modifier && key == KeyEvent.KEYCODE_5) { + return openConversationByIndex(4); + } else if (modifier && key == KeyEvent.KEYCODE_6) { + return openConversationByIndex(5); + } else if (modifier && key == KeyEvent.KEYCODE_7) { + return openConversationByIndex(6); + } else if (modifier && key == KeyEvent.KEYCODE_8) { + return openConversationByIndex(7); + } else if (modifier && key == KeyEvent.KEYCODE_9) { + return openConversationByIndex(8); + } else if (modifier && key == KeyEvent.KEYCODE_0) { + return openConversationByIndex(9); + } else { + return super.onKeyUp(key, event); + } + } + + private void toggleConversationsOverview() { + if (isConversationsOverviewVisable()) { + hideConversationsOverview(); + if (mConversationFragment != null) { + mConversationFragment.setFocusOnInputField(); + } + } else { + showConversationsOverview(); + } + } + + private boolean selectUpConversation() { + if (this.mSelectedConversation != null) { + int index = this.conversationList.indexOf(this.mSelectedConversation); + if (index > 0) { + return openConversationByIndex(index - 1); + } + } + return false; + } + + private boolean selectDownConversation() { + if (this.mSelectedConversation != null) { + int index = this.conversationList.indexOf(this.mSelectedConversation); + if (index != -1 && index < this.conversationList.size() - 1) { + return openConversationByIndex(index + 1); + } + } + return false; + } + + private boolean openConversationByIndex(int index) { + try { + this.conversationWasSelectedByKeyboard = true; + setSelectedConversation(this.conversationList.get(index)); + this.mConversationFragment.reInit(getSelectedConversation()); + if (index > listView.getLastVisiblePosition() - 1 || index < listView.getFirstVisiblePosition() + 1) { + this.listView.setSelection(index); + } + openConversation(); + return true; + } catch (IndexOutOfBoundsException e) { + return false; + } + } + + @Override + protected void onNewIntent(final Intent intent) { + if (intent != null && ACTION_VIEW_CONVERSATION.equals(intent.getAction())) { + mOpenConversation = null; + mUnprocessedNewIntent = true; + if (xmppConnectionServiceBound) { + handleViewConversationIntent(intent); + intent.setAction(Intent.ACTION_MAIN); + } else { + setIntent(intent); + } + } + } + + @Override + public void onStart() { + super.onStart(); + this.mRedirected.set(false); + if (this.xmppConnectionServiceBound) { + this.onBackendConnected(); + } + if (conversationList.size() >= 1) { + this.onConversationUpdate(); + } + } + + @Override + public void onPause() { + listView.discardUndo(); + super.onPause(); + this.mActivityPaused = true; + } + + @Override + public void onResume() { + super.onResume(); + final int theme = findTheme(); + final boolean usingEnterKey = usingEnterKey(); + if (this.mTheme != theme || usingEnterKey != mUsingEnterKey) { + recreate(); + } + this.mActivityPaused = false; + + if (!isConversationsOverviewVisable() || !isConversationsOverviewHideable()) { + sendReadMarkerIfNecessary(getSelectedConversation()); + } + } + + @Override + public void onSaveInstanceState(final Bundle savedInstanceState) { + Conversation conversation = getSelectedConversation(); + if (conversation != null) { + savedInstanceState.putString(STATE_OPEN_CONVERSATION, conversation.getUuid()); + Pair scrollPosition = mConversationFragment.getScrollPosition(); + if (scrollPosition != null) { + savedInstanceState.putInt(STATE_FIRST_VISIBLE, scrollPosition.first); + savedInstanceState.putInt(STATE_OFFSET_FROM_TOP, scrollPosition.second); + } + } else { + savedInstanceState.remove(STATE_OPEN_CONVERSATION); + } + savedInstanceState.putBoolean(STATE_PANEL_OPEN, isConversationsOverviewVisable()); + if (this.mPendingImageUris.size() >= 1) { + Log.d(Config.LOGTAG, "ConversationActivity.onSaveInstanceState() - saving pending image uri"); + savedInstanceState.putString(STATE_PENDING_IMAGE_URI, this.mPendingImageUris.get(0).toString()); + } else if (this.mPendingPhotoUris.size() >= 1) { + savedInstanceState.putString(STATE_PENDING_PHOTO_URI, this.mPendingPhotoUris.get(0).toString()); + } else { + savedInstanceState.remove(STATE_PENDING_IMAGE_URI); + savedInstanceState.remove(STATE_PENDING_PHOTO_URI); + } + super.onSaveInstanceState(savedInstanceState); + } + + private void clearPending() { + mPendingImageUris.clear(); + mPendingFileUris.clear(); + mPendingVideoUris.clear(); + mPendingPhotoUris.clear(); + mPendingGeoUri = null; + mPostponedActivityResult = null; + } + + @Override + void onBackendConnected() { + this.xmppConnectionService.getNotificationService().setIsInForeground(true); + updateConversationList(); + + Bundle extras = getIntent().getExtras(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (extras != null && extras.containsKey("FirstStart")) { + FirstStartTime = extras.getLong("FirstStart"); + Log.d(Config.LOGTAG, "Get first start time from StartUI: " + FirstStartTime); + } + } else { + FirstStartTime = System.currentTimeMillis(); + Log.d(Config.LOGTAG, "Device is running Android < SDK 23, no restart required: " + FirstStartTime); + } + + if (mPendingConferenceInvite != null) { + if (mPendingConferenceInvite.execute(this)) { + mToast = Toast.makeText(this, R.string.creating_conference, Toast.LENGTH_LONG); + mToast.show(); + } + mPendingConferenceInvite = null; + } + + if (FirstStartTime == 0) { Log.d(Config.LOGTAG, "First start time: " + FirstStartTime + ", restarting App"); //write first start timestamp to file String PREFS_NAME = "FirstStart"; @@ -1414,72 +1414,72 @@ public class ConversationActivity extends XmppActivity startActivity(intent); System.exit(0); } - final Intent intent = getIntent(); - if (xmppConnectionService.getAccounts().size() == 0) { - if (mRedirected.compareAndSet(false, true)) { - if (Config.X509_VERIFICATION) { + final Intent intent = getIntent(); + if (xmppConnectionService.getAccounts().size() == 0) { + if (mRedirected.compareAndSet(false, true)) { + if (Config.X509_VERIFICATION) { startActivity(new Intent(this, ManageAccountActivity.class)); } else if (Config.MAGIC_CREATE_DOMAIN != null) { Log.d(Config.LOGTAG, "First start time: " + FirstStartTime); startActivity(new Intent(this, WelcomeActivity.class)); } else { Intent editAccount = new Intent(this, EditAccountActivity.class); - editAccount.putExtra("init",true); + editAccount.putExtra("init", true); startActivity(editAccount); } finish(); - } - } else if (conversationList.size() <= 0) { - if (mRedirected.compareAndSet(false, true)) { - Account pendingAccount = xmppConnectionService.getPendingAccount(); - if (pendingAccount == null) { - Intent startConversationActivity = new Intent(this, StartConversationActivity.class); - intent.putExtra("init", true); - startActivity(startConversationActivity); - } else { - switchToAccount(pendingAccount, true); - } - finish(); - } - } else if (selectConversationByUuid(mOpenConversation)) { - if (mPanelOpen) { - showConversationsOverview(); - } else { - if (isConversationsOverviewHideable()) { - openConversation(); - updateActionBarTitle(true); - } - } + } + } else if (conversationList.size() <= 0) { + if (mRedirected.compareAndSet(false, true)) { + Account pendingAccount = xmppConnectionService.getPendingAccount(); + if (pendingAccount == null) { + Intent startConversationActivity = new Intent(this, StartConversationActivity.class); + intent.putExtra("init", true); + startActivity(startConversationActivity); + } else { + switchToAccount(pendingAccount, true); + } + finish(); + } + } else if (selectConversationByUuid(mOpenConversation)) { + if (mPanelOpen) { + showConversationsOverview(); + } else { + if (isConversationsOverviewHideable()) { + openConversation(); + updateActionBarTitle(true); + } + } if (this.mConversationFragment.reInit(getSelectedConversation())) { Log.d(Config.LOGTAG, "setting scroll position on fragment"); this.mConversationFragment.setScrollPosition(mScrollPosition); } mOpenConversation = null; } else if (intent != null && ACTION_VIEW_CONVERSATION.equals(intent.getAction())) { - clearPending(); - handleViewConversationIntent(intent); - intent.setAction(Intent.ACTION_MAIN); - } else if (getSelectedConversation() == null) { - showConversationsOverview(); - clearPending(); - setSelectedConversation(conversationList.get(0)); - this.mConversationFragment.reInit(getSelectedConversation()); - } else { - this.mConversationFragment.messageListAdapter.updatePreferences(); - this.mConversationFragment.messagesView.invalidateViews(); - this.mConversationFragment.setupIme(); - } + clearPending(); + handleViewConversationIntent(intent); + intent.setAction(Intent.ACTION_MAIN); + } else if (getSelectedConversation() == null) { + showConversationsOverview(); + clearPending(); + setSelectedConversation(conversationList.get(0)); + this.mConversationFragment.reInit(getSelectedConversation()); + } else { + this.mConversationFragment.messageListAdapter.updatePreferences(); + this.mConversationFragment.messagesView.invalidateViews(); + this.mConversationFragment.setupIme(); + } if (xmppConnectionService.getAccounts().size() != 0) { if (xmppConnectionService.hasInternetConnection()) { if (xmppConnectionService.isWIFI() || (xmppConnectionService.isMobile() && !xmppConnectionService.isMobileRoaming())) - AppUpdate(); + AppUpdate(); } } - if (this.mPostponedActivityResult != null) { - this.onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second); - } + if (this.mPostponedActivityResult != null) { + this.onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second); + } final boolean stopping; if (Build.VERSION.SDK_INT >= 17) { @@ -1488,193 +1488,193 @@ public class ConversationActivity extends XmppActivity stopping = isFinishing(); } - if (!forbidProcessingPendings) { + if (!forbidProcessingPendings) { int ImageUrisCount = mPendingImageUris.size(); if (ImageUrisCount == 1) { Uri uri = mPendingImageUris.get(0); - Log.d(Config.LOGTAG,"ConversationActivity.onBackendConnected() - attaching image to conversations. stopping="+Boolean.toString(stopping)); + Log.d(Config.LOGTAG, "ConversationActivity.onBackendConnected() - attaching image to conversations. stopping=" + Boolean.toString(stopping)); attachImageToConversation(getSelectedConversation(), uri); } else { for (Iterator i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) { Uri foo = i.next(); - Log.d(Config.LOGTAG,"ConversationActivity.onBackendConnected() - attaching images to conversations. stopping="+Boolean.toString(stopping)); + Log.d(Config.LOGTAG, "ConversationActivity.onBackendConnected() - attaching images to conversations. stopping=" + Boolean.toString(stopping)); attachImagesToConversation(getSelectedConversation(), foo); } } for (Iterator i = mPendingPhotoUris.iterator(); i.hasNext(); i.remove()) { - Log.d(Config.LOGTAG,"ConversationActivity.onBackendConnected() - attaching photo to conversations. stopping="+Boolean.toString(stopping)); + Log.d(Config.LOGTAG, "ConversationActivity.onBackendConnected() - attaching photo to conversations. stopping=" + Boolean.toString(stopping)); attachPhotoToConversation(getSelectedConversation(), i.next()); } for (Iterator i = mPendingVideoUris.iterator(); i.hasNext(); i.remove()) { - Log.d(Config.LOGTAG,"ConversationActivity.onBackendConnected() - attaching video to conversations. stopping="+Boolean.toString(stopping)); + Log.d(Config.LOGTAG, "ConversationActivity.onBackendConnected() - attaching video to conversations. stopping=" + Boolean.toString(stopping)); attachVideoToConversation(getSelectedConversation(), i.next()); } - for (Iterator i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) { - Log.d(Config.LOGTAG,"ConversationActivity.onBackendConnected() - attaching file to conversations. stopping="+Boolean.toString(stopping)); + for (Iterator i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) { + Log.d(Config.LOGTAG, "ConversationActivity.onBackendConnected() - attaching file to conversations. stopping=" + Boolean.toString(stopping)); attachFileToConversation(getSelectedConversation(), i.next()); - } - - if (mPendingGeoUri != null) { - attachLocationToConversation(getSelectedConversation(), mPendingGeoUri); - mPendingGeoUri = null; - } - } - forbidProcessingPendings = false; - - if (!ExceptionHelper.checkForCrash(this, this.xmppConnectionService)) { - openBatteryOptimizationDialogIfNeeded(); - } - } - - private void handleViewConversationIntent(final Intent intent) { - final String uuid = intent.getStringExtra(CONVERSATION); - final String downloadUuid = intent.getStringExtra(EXTRA_DOWNLOAD_UUID); - final String text = intent.getStringExtra(TEXT); - final String nick = intent.getStringExtra(NICK); - final boolean pm = intent.getBooleanExtra(PRIVATE_MESSAGE, false); - if (selectConversationByUuid(uuid)) { - this.mConversationFragment.reInit(getSelectedConversation()); - if (nick != null) { - if (pm) { - Jid jid = getSelectedConversation().getJid(); - try { - Jid next = Jid.fromParts(jid.getLocalpart(), jid.getDomainpart(), nick); - this.mConversationFragment.privateMessageWith(next); - } catch (final InvalidJidException ignored) { - //do nothing - } - } else { - this.mConversationFragment.highlightInConference(nick); - } - } else { - this.mConversationFragment.appendText(text); - } - hideConversationsOverview(); + } + + if (mPendingGeoUri != null) { + attachLocationToConversation(getSelectedConversation(), mPendingGeoUri); + mPendingGeoUri = null; + } + } + forbidProcessingPendings = false; + + if (!ExceptionHelper.checkForCrash(this, this.xmppConnectionService)) { + openBatteryOptimizationDialogIfNeeded(); + } + } + + private void handleViewConversationIntent(final Intent intent) { + final String uuid = intent.getStringExtra(CONVERSATION); + final String downloadUuid = intent.getStringExtra(EXTRA_DOWNLOAD_UUID); + final String text = intent.getStringExtra(TEXT); + final String nick = intent.getStringExtra(NICK); + final boolean pm = intent.getBooleanExtra(PRIVATE_MESSAGE, false); + if (selectConversationByUuid(uuid)) { + this.mConversationFragment.reInit(getSelectedConversation()); + if (nick != null) { + if (pm) { + Jid jid = getSelectedConversation().getJid(); + try { + Jid next = Jid.fromParts(jid.getLocalpart(), jid.getDomainpart(), nick); + this.mConversationFragment.privateMessageWith(next); + } catch (final InvalidJidException ignored) { + //do nothing + } + } else { + this.mConversationFragment.highlightInConference(nick); + } + } else { + this.mConversationFragment.appendText(text); + } + hideConversationsOverview(); mUnprocessedNewIntent = false; - openConversation(); - if (mContentView instanceof SlidingPaneLayout) { - updateActionBarTitle(true); //fixes bug where slp isn't properly closed yet - } - if (downloadUuid != null) { - final Message message = mSelectedConversation.findMessageWithFileAndUuid(downloadUuid); - if (message != null) { - startDownloadable(message); - } - } else { + openConversation(); + if (mContentView instanceof SlidingPaneLayout) { + updateActionBarTitle(true); //fixes bug where slp isn't properly closed yet + } + if (downloadUuid != null) { + final Message message = mSelectedConversation.findMessageWithFileAndUuid(downloadUuid); + if (message != null) { + startDownloadable(message); + } + } else { mUnprocessedNewIntent = false; } - } - } - - private boolean selectConversationByUuid(String uuid) { - if (uuid == null) { - return false; - } - for (Conversation aConversationList : conversationList) { - if (aConversationList.getUuid().equals(uuid)) { - setSelectedConversation(aConversationList); - return true; - } - } - return false; - } - - @Override - protected void unregisterListeners() { - super.unregisterListeners(); - xmppConnectionService.getNotificationService().setOpenConversation(null); - } - - @SuppressLint("NewApi") - private static List extractUriFromIntent(final Intent intent) { - List uris = new ArrayList<>(); - if (intent == null) { - return uris; - } - Uri uri = intent.getData(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && uri == null) { - final ClipData clipData = intent.getClipData(); - if (clipData != null) { - for (int i = 0; i < clipData.getItemCount(); ++i) { - uris.add(clipData.getItemAt(i).getUri()); - } - } - } else { - uris.add(uri); - } - return uris; - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, final Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (resultCode == RESULT_OK) { - if (requestCode == REQUEST_DECRYPT_PGP) { - mConversationFragment.onActivityResult(requestCode, resultCode, data); - } else if (requestCode == REQUEST_CHOOSE_PGP_ID) { - // the user chose OpenPGP for encryption and selected his key in the PGP provider - if (xmppConnectionServiceBound) { - if (data.getExtras().containsKey(OpenPgpApi.EXTRA_SIGN_KEY_ID)) { - // associate selected PGP keyId with the account - mSelectedConversation.getAccount().setPgpSignId(data.getExtras().getLong(OpenPgpApi.EXTRA_SIGN_KEY_ID)); - // we need to announce the key as described in XEP-027 - announcePgp(mSelectedConversation.getAccount(), null, onOpenPGPKeyPublished); - } else { - choosePgpSignId(mSelectedConversation.getAccount()); - } - this.mPostponedActivityResult = null; - } else { - this.mPostponedActivityResult = new Pair<>(requestCode, data); - } - } else if (requestCode == REQUEST_ANNOUNCE_PGP) { - if (xmppConnectionServiceBound) { - announcePgp(mSelectedConversation.getAccount(), mSelectedConversation, onOpenPGPKeyPublished); - this.mPostponedActivityResult = null; - } else { - this.mPostponedActivityResult = new Pair<>(requestCode, data); - } - } else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_IMAGE) { - mPendingImageUris.clear(); - mPendingImageUris.addAll(extractUriFromIntent(data)); - int ImageUrisCount = mPendingImageUris.size(); - if (xmppConnectionServiceBound) { + } + } + + private boolean selectConversationByUuid(String uuid) { + if (uuid == null) { + return false; + } + for (Conversation aConversationList : conversationList) { + if (aConversationList.getUuid().equals(uuid)) { + setSelectedConversation(aConversationList); + return true; + } + } + return false; + } + + @Override + protected void unregisterListeners() { + super.unregisterListeners(); + xmppConnectionService.getNotificationService().setOpenConversation(null); + } + + @SuppressLint("NewApi") + private static List extractUriFromIntent(final Intent intent) { + List uris = new ArrayList<>(); + if (intent == null) { + return uris; + } + Uri uri = intent.getData(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && uri == null) { + final ClipData clipData = intent.getClipData(); + if (clipData != null) { + for (int i = 0; i < clipData.getItemCount(); ++i) { + uris.add(clipData.getItemAt(i).getUri()); + } + } + } else { + uris.add(uri); + } + return uris; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, final Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode == RESULT_OK) { + if (requestCode == REQUEST_DECRYPT_PGP) { + mConversationFragment.onActivityResult(requestCode, resultCode, data); + } else if (requestCode == REQUEST_CHOOSE_PGP_ID) { + // the user chose OpenPGP for encryption and selected his key in the PGP provider + if (xmppConnectionServiceBound) { + if (data.getExtras().containsKey(OpenPgpApi.EXTRA_SIGN_KEY_ID)) { + // associate selected PGP keyId with the account + mSelectedConversation.getAccount().setPgpSignId(data.getExtras().getLong(OpenPgpApi.EXTRA_SIGN_KEY_ID)); + // we need to announce the key as described in XEP-027 + announcePgp(mSelectedConversation.getAccount(), null, onOpenPGPKeyPublished); + } else { + choosePgpSignId(mSelectedConversation.getAccount()); + } + this.mPostponedActivityResult = null; + } else { + this.mPostponedActivityResult = new Pair<>(requestCode, data); + } + } else if (requestCode == REQUEST_ANNOUNCE_PGP) { + if (xmppConnectionServiceBound) { + announcePgp(mSelectedConversation.getAccount(), mSelectedConversation, onOpenPGPKeyPublished); + this.mPostponedActivityResult = null; + } else { + this.mPostponedActivityResult = new Pair<>(requestCode, data); + } + } else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_IMAGE) { + mPendingImageUris.clear(); + mPendingImageUris.addAll(extractUriFromIntent(data)); + int ImageUrisCount = mPendingImageUris.size(); + if (xmppConnectionServiceBound) { if (ImageUrisCount == 1) { Uri uri = mPendingImageUris.get(0); - Log.d(Config.LOGTAG,"ConversationActivity.onActivityResult() - attaching image to conversations. CHOOSE_IMAGE"); + Log.d(Config.LOGTAG, "ConversationActivity.onActivityResult() - attaching image to conversations. CHOOSE_IMAGE"); attachImageToConversation(getSelectedConversation(), uri); } else { for (Iterator i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) { - Log.d(Config.LOGTAG,"ConversationActivity.onActivityResult() - attaching images to conversations. CHOOSE_IMAGES"); + Log.d(Config.LOGTAG, "ConversationActivity.onActivityResult() - attaching images to conversations. CHOOSE_IMAGES"); attachImagesToConversation(getSelectedConversation(), i.next()); } } - } - } else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_FILE || requestCode == ATTACHMENT_CHOICE_RECORD_VOICE) { - final List uris = extractUriFromIntent(data); - final Conversation c = getSelectedConversation(); - final OnPresenceSelected callback = new OnPresenceSelected() { - @Override - public void onPresenceSelected() { - mPendingFileUris.clear(); - mPendingFileUris.addAll(uris); - if (xmppConnectionServiceBound) { - for (Iterator i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) { - Log.d(Config.LOGTAG,"ConversationActivity.onActivityResult() - attaching file to conversations. CHOOSE_FILE/RECORD_VOICE"); - attachFileToConversation(c, i.next()); - } - } - } - }; - if (c == null || c.getMode() == Conversation.MODE_MULTI - || FileBackend.allFilesUnderSize(this, uris, getMaxHttpUploadSize(c)) - || c.getNextEncryption() == Message.ENCRYPTION_OTR) { - callback.onPresenceSelected(); - } else { - selectPresence(c, callback); - } + } + } else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_FILE || requestCode == ATTACHMENT_CHOICE_RECORD_VOICE) { + final List uris = extractUriFromIntent(data); + final Conversation c = getSelectedConversation(); + final OnPresenceSelected callback = new OnPresenceSelected() { + @Override + public void onPresenceSelected() { + mPendingFileUris.clear(); + mPendingFileUris.addAll(uris); + if (xmppConnectionServiceBound) { + for (Iterator i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) { + Log.d(Config.LOGTAG, "ConversationActivity.onActivityResult() - attaching file to conversations. CHOOSE_FILE/RECORD_VOICE"); + attachFileToConversation(c, i.next()); + } + } + } + }; + if (c == null || c.getMode() == Conversation.MODE_MULTI + || FileBackend.allFilesUnderSize(this, uris, getMaxHttpUploadSize(c)) + || c.getNextEncryption() == Message.ENCRYPTION_OTR) { + callback.onPresenceSelected(); + } else { + selectPresence(c, callback); + } } else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_VIDEO) { final List uris = extractUriFromIntent(data); final Conversation c = getSelectedConversation(); @@ -1685,7 +1685,7 @@ public class ConversationActivity extends XmppActivity mPendingVideoUris.addAll(uris); if (xmppConnectionServiceBound) { for (Iterator i = mPendingVideoUris.iterator(); i.hasNext(); i.remove()) { - Log.d(Config.LOGTAG,"ConversationActivity.onActivityResult() - attaching video to conversations. CHOOSE_VIDEO"); + Log.d(Config.LOGTAG, "ConversationActivity.onActivityResult() - attaching video to conversations. CHOOSE_VIDEO"); attachVideoToConversation(c, i.next()); } } @@ -1698,163 +1698,163 @@ public class ConversationActivity extends XmppActivity } else { selectPresence(c, callback); } - } else if (requestCode == ATTACHMENT_CHOICE_TAKE_PHOTO) { - if (mPendingPhotoUris.size() == 1) { + } else if (requestCode == ATTACHMENT_CHOICE_TAKE_PHOTO) { + if (mPendingPhotoUris.size() == 1) { Uri uri = FileBackend.getIndexableTakePhotoUri(mPendingPhotoUris.get(0)); - mPendingPhotoUris.set(0, uri); + mPendingPhotoUris.set(0, uri); if (xmppConnectionServiceBound) { - Log.d(Config.LOGTAG,"ConversationActivity.onActivityResult() - attaching photo to conversations. TAKE_PHOTO"); + Log.d(Config.LOGTAG, "ConversationActivity.onActivityResult() - attaching photo to conversations. TAKE_PHOTO"); attachPhotoToConversation(getSelectedConversation(), uri); - mPendingPhotoUris.clear(); - } - Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); + mPendingPhotoUris.clear(); + } + Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); intent.setData(uri); - sendBroadcast(intent); - } else { - mPendingPhotoUris.clear(); - } + sendBroadcast(intent); + } else { + mPendingPhotoUris.clear(); + } } else if (requestCode == ATTACHMENT_CHOICE_LOCATION) { - double latitude = data.getDoubleExtra("latitude", 0); - double longitude = data.getDoubleExtra("longitude", 0); - this.mPendingGeoUri = Uri.parse("geo:" + String.valueOf(latitude) + "," + String.valueOf(longitude)); - if (xmppConnectionServiceBound) { - attachLocationToConversation(getSelectedConversation(), mPendingGeoUri); - this.mPendingGeoUri = null; - } - } else if (requestCode == REQUEST_TRUST_KEYS_TEXT || requestCode == REQUEST_TRUST_KEYS_MENU) { - this.forbidProcessingPendings = !xmppConnectionServiceBound; - if (xmppConnectionServiceBound) { - mConversationFragment.onActivityResult(requestCode, resultCode, data); - this.mPostponedActivityResult = null; - } else { - this.mPostponedActivityResult = new Pair<>(requestCode, data); - } - - } - } else { - mPendingImageUris.clear(); - mPendingFileUris.clear(); + double latitude = data.getDoubleExtra("latitude", 0); + double longitude = data.getDoubleExtra("longitude", 0); + this.mPendingGeoUri = Uri.parse("geo:" + String.valueOf(latitude) + "," + String.valueOf(longitude)); + if (xmppConnectionServiceBound) { + attachLocationToConversation(getSelectedConversation(), mPendingGeoUri); + this.mPendingGeoUri = null; + } + } else if (requestCode == REQUEST_TRUST_KEYS_TEXT || requestCode == REQUEST_TRUST_KEYS_MENU) { + this.forbidProcessingPendings = !xmppConnectionServiceBound; + if (xmppConnectionServiceBound) { + mConversationFragment.onActivityResult(requestCode, resultCode, data); + this.mPostponedActivityResult = null; + } else { + this.mPostponedActivityResult = new Pair<>(requestCode, data); + } + + } + } else { + mPendingImageUris.clear(); + mPendingFileUris.clear(); mPendingPhotoUris.clear(); - if (requestCode == ConversationActivity.REQUEST_DECRYPT_PGP) { - mConversationFragment.onActivityResult(requestCode, resultCode, data); - } - if (requestCode == REQUEST_BATTERY_OP) { - setNeverAskForBatteryOptimizationsAgain(); - } - } - } - - private long getMaxHttpUploadSize(Conversation conversation) { + if (requestCode == ConversationActivity.REQUEST_DECRYPT_PGP) { + mConversationFragment.onActivityResult(requestCode, resultCode, data); + } + if (requestCode == REQUEST_BATTERY_OP) { + setNeverAskForBatteryOptimizationsAgain(); + } + } + } + + private long getMaxHttpUploadSize(Conversation conversation) { final XmppConnection connection = conversation.getAccount().getXmppConnection(); return connection == null ? -1 : connection.getFeatures().getMaxHttpUploadSize(); - } - - private void setNeverAskForBatteryOptimizationsAgain() { - getPreferences().edit().putBoolean("show_battery_optimization", false).commit(); - } - - private void openBatteryOptimizationDialogIfNeeded() { - if (hasAccountWithoutPush() - && isOptimizingBattery() - && getPreferences().getBoolean("show_battery_optimization", true)) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.battery_optimizations_enabled); - builder.setMessage(R.string.battery_optimizations_enabled_dialog); - builder.setPositiveButton(R.string.next, new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); - Uri uri = Uri.parse("package:" + getPackageName()); - intent.setData(uri); - try { - startActivityForResult(intent, REQUEST_BATTERY_OP); - } catch (ActivityNotFoundException e) { - Toast.makeText(ConversationActivity.this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show(); - } - } - }); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - builder.setOnDismissListener(new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - setNeverAskForBatteryOptimizationsAgain(); - } - }); - } - builder.create().show(); - } - } - - private boolean hasAccountWithoutPush() { - for(Account account : xmppConnectionService.getAccounts()) { - if (account.getStatus() != Account.State.DISABLED - && !xmppConnectionService.getPushManagementService().available(account)) { - return true; - } - } - return false; - } - - private void attachLocationToConversation(Conversation conversation, Uri uri) { - if (conversation == null) { - return; - } - xmppConnectionService.attachLocationToConversation(conversation, uri, new UiCallback() { - - @Override - public void success(Message message) { - xmppConnectionService.sendMessage(message); - } - - @Override - public void error(int errorCode, Message object) { - - } - - @Override - public void userInputRequried(PendingIntent pi, Message object) { - - } - }); - } - - private void attachFileToConversation(Conversation conversation, Uri uri) { - if (conversation == null) { - return; - } - final Toast prepareFileToast = Toast.makeText(getApplicationContext(),getText(R.string.preparing_file), Toast.LENGTH_LONG); - prepareFileToast.show(); - xmppConnectionService.attachFileToConversation(conversation, uri, new UiCallback() { - @Override - public void success(Message message) { - hidePrepareFileToast(prepareFileToast); - xmppConnectionService.sendMessage(message); - } - - @Override - public void error(final int errorCode, Message message) { - hidePrepareFileToast(prepareFileToast); - runOnUiThread(new Runnable() { - @Override - public void run() { - replaceToast(getString(errorCode)); - } - }); - - } - - @Override - public void userInputRequried(PendingIntent pi, Message message) { - hidePrepareFileToast(prepareFileToast); - } - }); - } + } + + private void setNeverAskForBatteryOptimizationsAgain() { + getPreferences().edit().putBoolean("show_battery_optimization", false).commit(); + } + + private void openBatteryOptimizationDialogIfNeeded() { + if (hasAccountWithoutPush() + && isOptimizingBattery() + && getPreferences().getBoolean("show_battery_optimization", true)) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.battery_optimizations_enabled); + builder.setMessage(R.string.battery_optimizations_enabled_dialog); + builder.setPositiveButton(R.string.next, new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); + Uri uri = Uri.parse("package:" + getPackageName()); + intent.setData(uri); + try { + startActivityForResult(intent, REQUEST_BATTERY_OP); + } catch (ActivityNotFoundException e) { + Toast.makeText(ConversationActivity.this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show(); + } + } + }); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + builder.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + setNeverAskForBatteryOptimizationsAgain(); + } + }); + } + builder.create().show(); + } + } + + private boolean hasAccountWithoutPush() { + for (Account account : xmppConnectionService.getAccounts()) { + if (account.getStatus() != Account.State.DISABLED + && !xmppConnectionService.getPushManagementService().available(account)) { + return true; + } + } + return false; + } + + private void attachLocationToConversation(Conversation conversation, Uri uri) { + if (conversation == null) { + return; + } + xmppConnectionService.attachLocationToConversation(conversation, uri, new UiCallback() { + + @Override + public void success(Message message) { + xmppConnectionService.sendMessage(message); + } + + @Override + public void error(int errorCode, Message object) { + + } + + @Override + public void userInputRequried(PendingIntent pi, Message object) { + + } + }); + } + + private void attachFileToConversation(Conversation conversation, Uri uri) { + if (conversation == null) { + return; + } + final Toast prepareFileToast = Toast.makeText(getApplicationContext(), getText(R.string.preparing_file), Toast.LENGTH_LONG); + prepareFileToast.show(); + xmppConnectionService.attachFileToConversation(conversation, uri, new UiCallback() { + @Override + public void success(Message message) { + hidePrepareFileToast(prepareFileToast); + xmppConnectionService.sendMessage(message); + } + + @Override + public void error(final int errorCode, Message message) { + hidePrepareFileToast(prepareFileToast); + runOnUiThread(new Runnable() { + @Override + public void run() { + replaceToast(getString(errorCode)); + } + }); + + } + + @Override + public void userInputRequried(PendingIntent pi, Message message) { + hidePrepareFileToast(prepareFileToast); + } + }); + } private void attachVideoToConversation(Conversation conversation, final Uri uri) { if (conversation == null) { return; } - final Toast prepareFileToast = Toast.makeText(getApplicationContext(),getText(R.string.preparing_video), Toast.LENGTH_LONG); + final Toast prepareFileToast = Toast.makeText(getApplicationContext(), getText(R.string.preparing_video), Toast.LENGTH_LONG); prepareFileToast.show(); xmppConnectionService.attachVideoToConversation(conversation, uri, new UiCallback() { @Override @@ -1886,7 +1886,40 @@ public class ConversationActivity extends XmppActivity if (conversation == null) { return; } - final Toast prepareFileToast = Toast.makeText(getApplicationContext(),getText(R.string.preparing_image), Toast.LENGTH_LONG); + final Toast prepareFileToast = Toast.makeText(getApplicationContext(), getText(R.string.preparing_image), Toast.LENGTH_LONG); + prepareFileToast.show(); + xmppConnectionService.attachImageToConversation(conversation, uri, + new UiCallback() { + + @Override + public void userInputRequried(PendingIntent pi, Message object) { + hidePrepareFileToast(prepareFileToast); + } + + @Override + public void success(Message message) { + hidePrepareFileToast(prepareFileToast); + xmppConnectionService.sendMessage(message); + } + + @Override + public void error(final int error, Message message) { + hidePrepareFileToast(prepareFileToast); + runOnUiThread(new Runnable() { + @Override + public void run() { + replaceToast(getString(error)); + } + }); + } + }); + } + + private void attachImagesToConversation(Conversation conversation, Uri uri) { + if (conversation == null) { + return; + } + final Toast prepareFileToast = Toast.makeText(getApplicationContext(), getText(R.string.preparing_image), Toast.LENGTH_LONG); prepareFileToast.show(); xmppConnectionService.attachImageToConversation(conversation, uri, new UiCallback() { @@ -1915,327 +1948,294 @@ public class ConversationActivity extends XmppActivity }); } - private void attachImagesToConversation(Conversation conversation, Uri uri) { - if (conversation == null) { - return; - } - final Toast prepareFileToast = Toast.makeText(getApplicationContext(),getText(R.string.preparing_image), Toast.LENGTH_LONG); - prepareFileToast.show(); - xmppConnectionService.attachImageToConversation(conversation, uri, - new UiCallback() { - - @Override - public void userInputRequried(PendingIntent pi, Message object) { - hidePrepareFileToast(prepareFileToast); - } - - @Override - public void success(Message message) { - hidePrepareFileToast(prepareFileToast); - xmppConnectionService.sendMessage(message); - } - - @Override - public void error(final int error, Message message) { - hidePrepareFileToast(prepareFileToast); - runOnUiThread(new Runnable() { - @Override - public void run() { - replaceToast(getString(error)); - } - }); - } - }); - } - - private void attachImageToConversation(Conversation conversation, Uri uri) { - if (conversation == null) { - return; - } - final Conversation conversation_preview = conversation; - final Uri uri_preview = uri; - Bitmap bitmap = BitmapFactory.decodeFile(FileUtils.getPath(this, uri)); - File file = new File(FileUtils.getPath(this, uri)); - ExifInterface exif = null; - try { - exif = new ExifInterface(file.toString()); - } catch (IOException e) { - e.printStackTrace(); - } - int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); - Log.d(Config.LOGTAG, "EXIF: " + orientation); - Bitmap rotated_image = null; - Log.d(Config.LOGTAG, "Rotate image"); - rotated_image = FileBackend.rotateBitmap(file, bitmap, orientation); - if (rotated_image != null) { - int scaleSize = 600; - int originalWidth = rotated_image.getWidth(); - int originalHeight = rotated_image.getHeight(); - int newWidth = -1; - int newHeight = -1; - float multFactor; - if (originalHeight > originalWidth) { - newHeight = scaleSize; - multFactor = (float) originalWidth / (float) originalHeight; - newWidth = (int) (newHeight * multFactor); - } else if (originalWidth > originalHeight) { - newWidth = scaleSize; - multFactor = (float) originalHeight / (float) originalWidth; - newHeight = (int) (newWidth * multFactor); - } else if (originalHeight == originalWidth) { - newHeight = scaleSize; - newWidth = scaleSize; - } - Log.d(Config.LOGTAG, "Scaling preview image from " + originalHeight + "px x " + originalWidth + "px to " + newHeight + "px x " + newWidth + "px"); - Bitmap preview = Bitmap.createScaledBitmap(rotated_image, newWidth, newHeight, false); - ImageView ImagePreview = new ImageView(this); - LinearLayout.LayoutParams vp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT); - ImagePreview.setLayoutParams(vp); - ImagePreview.setMaxWidth(newWidth); - ImagePreview.setMaxHeight(newHeight); - //ImagePreview.setScaleType(ImageView.ScaleType.FIT_XY); - //ImagePreview.setAdjustViewBounds(true); - ImagePreview.setPadding(5, 5, 5, 5); - ImagePreview.setImageBitmap(preview); - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setView(ImagePreview); - builder.setTitle(R.string.send_image); - builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(final DialogInterface dialog, final int which) { - final Toast prepareFileToast = Toast.makeText(getApplicationContext(), getText(R.string.preparing_image), Toast.LENGTH_LONG); - prepareFileToast.show(); - xmppConnectionService.attachImageToConversation(conversation_preview, uri_preview, - new UiCallback() { - - @Override - public void userInputRequried(PendingIntent pi, Message object) { - hidePrepareFileToast(prepareFileToast); - } - - @Override - public void success(Message message) { - hidePrepareFileToast(prepareFileToast); - xmppConnectionService.sendMessage(message); - } - - @Override - public void error(final int error, Message message) { - hidePrepareFileToast(prepareFileToast); - runOnUiThread(new Runnable() { - @Override - public void run() { - replaceToast(getString(error)); - } - }); - } - }); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - mPendingImageUris.clear(); - } - }); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - builder.setOnDismissListener(new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - mPendingImageUris.clear(); - } - }); - } - AlertDialog alertDialog = builder.create(); - alertDialog.show(); - } else { - Toast.makeText(getApplicationContext(), getText(R.string.error_file_not_found), Toast.LENGTH_LONG).show(); - } - } - - private void hidePrepareFileToast(final Toast prepareFileToast) { - if (prepareFileToast != null) { - runOnUiThread(new Runnable() { - - @Override - public void run() { - prepareFileToast.cancel(); - } - }); - } - } - - public void updateConversationList() { - xmppConnectionService - .populateWithOrderedConversations(conversationList); - if (swipedConversation != null) { - if (swipedConversation.isRead()) { - conversationList.remove(swipedConversation); - } else { - listView.discardUndo(); - } - } - listAdapter.notifyDataSetChanged(); - } - - public void runIntent(PendingIntent pi, int requestCode) { - try { - this.startIntentSenderForResult(pi.getIntentSender(), requestCode, - null, 0, 0, 0); - } catch (final SendIntentException ignored) { - } - } - - public void encryptTextMessage(Message message) { - xmppConnectionService.getPgpEngine().encrypt(message, - new UiCallback() { - - @Override - public void userInputRequried(PendingIntent pi,Message message) { - ConversationActivity.this.runIntent(pi,ConversationActivity.REQUEST_SEND_MESSAGE); - } - - @Override - public void success(Message message) { - message.setEncryption(Message.ENCRYPTION_DECRYPTED); - xmppConnectionService.sendMessage(message); - if (mConversationFragment != null) { - mConversationFragment.messageSent(); - } - } - - @Override - public void error(final int error, Message message) { - runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(ConversationActivity.this, - R.string.unable_to_connect_to_keychain, - Toast.LENGTH_SHORT - ).show(); - } - }); - } - }); - } - - public boolean useSendButtonToIndicateStatus() { - return getPreferences().getBoolean("send_button_status", true); - } - - public boolean indicateReceived() { - return getPreferences().getBoolean("indicate_received", true); - } - - public boolean useWhiteBackground() { - return getPreferences().getBoolean("use_white_background", false); - } - - protected boolean trustKeysIfNeeded(int requestCode) { - return trustKeysIfNeeded(requestCode, ATTACHMENT_CHOICE_INVALID); - } - - protected boolean trustKeysIfNeeded(int requestCode, int attachmentChoice) { - AxolotlService axolotlService = mSelectedConversation.getAccount().getAxolotlService(); - final List targets = axolotlService.getCryptoTargets(mSelectedConversation); - boolean hasUnaccepted = !mSelectedConversation.getAcceptedCryptoTargets().containsAll(targets); - boolean hasUndecidedOwn = !axolotlService.getKeysWithTrust(FingerprintStatus.createActiveUndecided()).isEmpty(); - boolean hasUndecidedContacts = !axolotlService.getKeysWithTrust(FingerprintStatus.createActiveUndecided(), targets).isEmpty(); - boolean hasPendingKeys = !axolotlService.findDevicesWithoutSession(mSelectedConversation).isEmpty(); - boolean hasNoTrustedKeys = axolotlService.anyTargetHasNoTrustedKeys(targets); - if(hasUndecidedOwn || hasUndecidedContacts || hasPendingKeys || hasNoTrustedKeys || hasUnaccepted) { - axolotlService.createSessionsIfNeeded(mSelectedConversation); - Intent intent = new Intent(getApplicationContext(), TrustKeysActivity.class); - String[] contacts = new String[targets.size()]; - for(int i = 0; i < contacts.length; ++i) { - contacts[i] = targets.get(i).toString(); - } - intent.putExtra("contacts", contacts); - intent.putExtra(EXTRA_ACCOUNT, mSelectedConversation.getAccount().getJid().toBareJid().toString()); - intent.putExtra("choice", attachmentChoice); - intent.putExtra("conversation",mSelectedConversation.getUuid()); - startActivityForResult(intent, requestCode); - return true; - } else { - return false; - } - } - - @Override - protected void refreshUiReal() { - updateConversationList(); - if (conversationList.size() > 0) { - if (!this.mConversationFragment.isAdded()) { - Log.d(Config.LOGTAG,"fragment NOT added to activity. detached="+Boolean.toString(mConversationFragment.isDetached())); - } - ConversationActivity.this.mConversationFragment.updateMessages(); - updateActionBarTitle(); - invalidateOptionsMenu(); - } else { - Log.d(Config.LOGTAG,"not updating conversations fragment because conversations list size was 0"); - } - } - - @Override - public void onAccountUpdate() { - this.refreshUi(); - } - - @Override - public void onConversationUpdate() { - this.refreshUi(); - } - - @Override - public void onRosterUpdate() { - this.refreshUi(); - } - - @Override - public void OnUpdateBlocklist(Status status) { - this.refreshUi(); - } - - public void unblockConversation(final Blockable conversation) { - xmppConnectionService.sendUnblockRequest(conversation); - } - - public boolean enterIsSend() { - return getPreferences().getBoolean("enter_is_send",false); - } - - @Override - public void onShowErrorToast(final int resId) { - runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(ConversationActivity.this,resId,Toast.LENGTH_SHORT).show(); - } - }); - } - - public boolean highlightSelectedConversations() { - return !isConversationsOverviewHideable() || this.conversationWasSelectedByKeyboard; - } - - @Override - public void onClick(View view) { - final Conversation conversation = getSelectedConversation(); - if (conversation.getMode() == Conversation.MODE_SINGLE) { - switchToContactDetails(getSelectedConversation().getContact()); - } else if (conversation.getMode() == Conversation.MODE_MULTI) { - Intent intent = new Intent(this, - ConferenceDetailsActivity.class); - intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC); - intent.putExtra("uuid", getSelectedConversation().getUuid()); - startActivity(intent); - } - } - - public void setMessagesLoaded() { - if (mConversationFragment != null) { - mConversationFragment.setMessagesLoaded(); - mConversationFragment.updateMessages(); - } - } + private void attachImageToConversation(Conversation conversation, Uri uri) { + if (conversation == null) { + return; + } + final Conversation conversation_preview = conversation; + final Uri uri_preview = uri; + Bitmap bitmap = BitmapFactory.decodeFile(FileUtils.getPath(this, uri)); + File file = new File(FileUtils.getPath(this, uri)); + ExifInterface exif = null; + try { + exif = new ExifInterface(file.toString()); + } catch (IOException e) { + e.printStackTrace(); + } + int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); + Log.d(Config.LOGTAG, "EXIF: " + orientation); + Bitmap rotated_image = null; + Log.d(Config.LOGTAG, "Rotate image"); + rotated_image = FileBackend.rotateBitmap(file, bitmap, orientation); + if (rotated_image != null) { + int scaleSize = 600; + int originalWidth = rotated_image.getWidth(); + int originalHeight = rotated_image.getHeight(); + int newWidth = -1; + int newHeight = -1; + float multFactor; + if (originalHeight > originalWidth) { + newHeight = scaleSize; + multFactor = (float) originalWidth / (float) originalHeight; + newWidth = (int) (newHeight * multFactor); + } else if (originalWidth > originalHeight) { + newWidth = scaleSize; + multFactor = (float) originalHeight / (float) originalWidth; + newHeight = (int) (newWidth * multFactor); + } else if (originalHeight == originalWidth) { + newHeight = scaleSize; + newWidth = scaleSize; + } + Log.d(Config.LOGTAG, "Scaling preview image from " + originalHeight + "px x " + originalWidth + "px to " + newHeight + "px x " + newWidth + "px"); + Bitmap preview = Bitmap.createScaledBitmap(rotated_image, newWidth, newHeight, false); + ImageView ImagePreview = new ImageView(this); + LinearLayout.LayoutParams vp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT); + ImagePreview.setLayoutParams(vp); + ImagePreview.setMaxWidth(newWidth); + ImagePreview.setMaxHeight(newHeight); + //ImagePreview.setScaleType(ImageView.ScaleType.FIT_XY); + //ImagePreview.setAdjustViewBounds(true); + ImagePreview.setPadding(5, 5, 5, 5); + ImagePreview.setImageBitmap(preview); + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setView(ImagePreview); + builder.setTitle(R.string.send_image); + builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(final DialogInterface dialog, final int which) { + final Toast prepareFileToast = Toast.makeText(getApplicationContext(), getText(R.string.preparing_image), Toast.LENGTH_LONG); + prepareFileToast.show(); + xmppConnectionService.attachImageToConversation(conversation_preview, uri_preview, + new UiCallback() { + + @Override + public void userInputRequried(PendingIntent pi, Message object) { + hidePrepareFileToast(prepareFileToast); + } + + @Override + public void success(Message message) { + hidePrepareFileToast(prepareFileToast); + xmppConnectionService.sendMessage(message); + } + + @Override + public void error(final int error, Message message) { + hidePrepareFileToast(prepareFileToast); + runOnUiThread(new Runnable() { + @Override + public void run() { + replaceToast(getString(error)); + } + }); + } + }); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + mPendingImageUris.clear(); + } + }); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + builder.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + mPendingImageUris.clear(); + } + }); + } + AlertDialog alertDialog = builder.create(); + alertDialog.show(); + } else { + Toast.makeText(getApplicationContext(), getText(R.string.error_file_not_found), Toast.LENGTH_LONG).show(); + } + } + + private void hidePrepareFileToast(final Toast prepareFileToast) { + if (prepareFileToast != null) { + runOnUiThread(new Runnable() { + + @Override + public void run() { + prepareFileToast.cancel(); + } + }); + } + } + + public void updateConversationList() { + xmppConnectionService + .populateWithOrderedConversations(conversationList); + if (swipedConversation != null) { + if (swipedConversation.isRead()) { + conversationList.remove(swipedConversation); + } else { + listView.discardUndo(); + } + } + listAdapter.notifyDataSetChanged(); + } + + public void runIntent(PendingIntent pi, int requestCode) { + try { + this.startIntentSenderForResult(pi.getIntentSender(), requestCode, + null, 0, 0, 0); + } catch (final SendIntentException ignored) { + } + } + + public void encryptTextMessage(Message message) { + xmppConnectionService.getPgpEngine().encrypt(message, + new UiCallback() { + + @Override + public void userInputRequried(PendingIntent pi, Message message) { + ConversationActivity.this.runIntent(pi, ConversationActivity.REQUEST_SEND_MESSAGE); + } + + @Override + public void success(Message message) { + message.setEncryption(Message.ENCRYPTION_DECRYPTED); + xmppConnectionService.sendMessage(message); + if (mConversationFragment != null) { + mConversationFragment.messageSent(); + } + } + + @Override + public void error(final int error, Message message) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(ConversationActivity.this, + R.string.unable_to_connect_to_keychain, + Toast.LENGTH_SHORT + ).show(); + } + }); + } + }); + } + + public boolean useSendButtonToIndicateStatus() { + return getPreferences().getBoolean("send_button_status", true); + } + + public boolean indicateReceived() { + return getPreferences().getBoolean("indicate_received", true); + } + + public boolean useWhiteBackground() { + return getPreferences().getBoolean("use_white_background", false); + } + + protected boolean trustKeysIfNeeded(int requestCode) { + return trustKeysIfNeeded(requestCode, ATTACHMENT_CHOICE_INVALID); + } + + protected boolean trustKeysIfNeeded(int requestCode, int attachmentChoice) { + AxolotlService axolotlService = mSelectedConversation.getAccount().getAxolotlService(); + final List targets = axolotlService.getCryptoTargets(mSelectedConversation); + boolean hasUnaccepted = !mSelectedConversation.getAcceptedCryptoTargets().containsAll(targets); + boolean hasUndecidedOwn = !axolotlService.getKeysWithTrust(FingerprintStatus.createActiveUndecided()).isEmpty(); + boolean hasUndecidedContacts = !axolotlService.getKeysWithTrust(FingerprintStatus.createActiveUndecided(), targets).isEmpty(); + boolean hasPendingKeys = !axolotlService.findDevicesWithoutSession(mSelectedConversation).isEmpty(); + boolean hasNoTrustedKeys = axolotlService.anyTargetHasNoTrustedKeys(targets); + if (hasUndecidedOwn || hasUndecidedContacts || hasPendingKeys || hasNoTrustedKeys || hasUnaccepted) { + axolotlService.createSessionsIfNeeded(mSelectedConversation); + Intent intent = new Intent(getApplicationContext(), TrustKeysActivity.class); + String[] contacts = new String[targets.size()]; + for (int i = 0; i < contacts.length; ++i) { + contacts[i] = targets.get(i).toString(); + } + intent.putExtra("contacts", contacts); + intent.putExtra(EXTRA_ACCOUNT, mSelectedConversation.getAccount().getJid().toBareJid().toString()); + intent.putExtra("choice", attachmentChoice); + intent.putExtra("conversation", mSelectedConversation.getUuid()); + startActivityForResult(intent, requestCode); + return true; + } else { + return false; + } + } + + @Override + protected void refreshUiReal() { + updateConversationList(); + if (conversationList.size() > 0) { + if (!this.mConversationFragment.isAdded()) { + Log.d(Config.LOGTAG, "fragment NOT added to activity. detached=" + Boolean.toString(mConversationFragment.isDetached())); + } + ConversationActivity.this.mConversationFragment.updateMessages(); + updateActionBarTitle(); + invalidateOptionsMenu(); + } else { + Log.d(Config.LOGTAG, "not updating conversations fragment because conversations list size was 0"); + } + } + + @Override + public void onAccountUpdate() { + this.refreshUi(); + } + + @Override + public void onConversationUpdate() { + this.refreshUi(); + } + + @Override + public void onRosterUpdate() { + this.refreshUi(); + } + + @Override + public void OnUpdateBlocklist(Status status) { + this.refreshUi(); + } + + public void unblockConversation(final Blockable conversation) { + xmppConnectionService.sendUnblockRequest(conversation); + } + + public boolean enterIsSend() { + return getPreferences().getBoolean("enter_is_send", false); + } + + @Override + public void onShowErrorToast(final int resId) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(ConversationActivity.this, resId, Toast.LENGTH_SHORT).show(); + } + }); + } + + public boolean highlightSelectedConversations() { + return !isConversationsOverviewHideable() || this.conversationWasSelectedByKeyboard; + } + + @Override + public void onClick(View view) { + final Conversation conversation = getSelectedConversation(); + if (conversation.getMode() == Conversation.MODE_SINGLE) { + switchToContactDetails(getSelectedConversation().getContact()); + } else if (conversation.getMode() == Conversation.MODE_MULTI) { + Intent intent = new Intent(this, + ConferenceDetailsActivity.class); + intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC); + intent.putExtra("uuid", getSelectedConversation().getUuid()); + startActivity(intent); + } + } + + public void setMessagesLoaded() { + if (mConversationFragment != null) { + mConversationFragment.setMessagesLoaded(); + mConversationFragment.updateMessages(); + } + } } diff --git a/src/main/java/de/pixart/messenger/ui/ConversationFragment.java b/src/main/java/de/pixart/messenger/ui/ConversationFragment.java index c71be325a..01fc47afe 100644 --- a/src/main/java/de/pixart/messenger/ui/ConversationFragment.java +++ b/src/main/java/de/pixart/messenger/ui/ConversationFragment.java @@ -73,152 +73,152 @@ import de.pixart.messenger.xmpp.jid.Jid; public class ConversationFragment extends Fragment implements EditMessage.KeyboardListener { - protected Conversation conversation; - private OnClickListener leaveMuc = new OnClickListener() { - - @Override - public void onClick(View v) { - activity.endConversation(conversation); - } - }; - private OnClickListener joinMuc = new OnClickListener() { - - @Override - public void onClick(View v) { - activity.xmppConnectionService.joinMuc(conversation); - } - }; - private OnClickListener enterPassword = new OnClickListener() { - - @Override - public void onClick(View v) { - MucOptions muc = conversation.getMucOptions(); - String password = muc.getPassword(); - if (password == null) { - password = ""; - } - activity.quickPasswordEdit(password, new OnValueEdited() { - - @Override - public void onValueEdited(String value) { - activity.xmppConnectionService.providePasswordForMuc( - conversation, value); - } - }); - } - }; - protected ListView messagesView; - final protected List messageList = new ArrayList<>(); - protected MessageAdapter messageListAdapter; - private EditMessage mEditMessage; - private ImageButton mSendButton; - private RelativeLayout snackbar; - private TextView snackbarMessage; - private TextView snackbarAction; - private boolean messagesLoaded = true; - private Toast messageLoaderToast; - - private OnScrollListener mOnScrollListener = new OnScrollListener() { - - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - // TODO Auto-generated method stub - - } - - @Override - public void onScroll(AbsListView view, int firstVisibleItem, - int visibleItemCount, int totalItemCount) { - synchronized (ConversationFragment.this.messageList) { - if (firstVisibleItem < 5 && messagesLoaded && messageList.size() > 0) { - long timestamp; - if (messageList.get(0).getType() == Message.TYPE_STATUS && messageList.size() >= 2) { - timestamp = messageList.get(1).getTimeSent(); - } else { - timestamp = messageList.get(0).getTimeSent(); - } - messagesLoaded = false; - activity.xmppConnectionService.loadMoreMessages(conversation, timestamp, new XmppConnectionService.OnMoreMessagesLoaded() { - @Override - public void onMoreMessagesLoaded(final int c, Conversation conversation) { - if (ConversationFragment.this.conversation != conversation) { - return; - } - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - final int oldPosition = messagesView.getFirstVisiblePosition(); - final Message message; - if (oldPosition < messageList.size()) { - message = messageList.get(oldPosition); - } else { - message = null; - } - String uuid = message != null ? message.getUuid() : null; - View v = messagesView.getChildAt(0); - final int pxOffset = (v == null) ? 0 : v.getTop(); - ConversationFragment.this.conversation.populateWithMessages(ConversationFragment.this.messageList); + protected Conversation conversation; + private OnClickListener leaveMuc = new OnClickListener() { + + @Override + public void onClick(View v) { + activity.endConversation(conversation); + } + }; + private OnClickListener joinMuc = new OnClickListener() { + + @Override + public void onClick(View v) { + activity.xmppConnectionService.joinMuc(conversation); + } + }; + private OnClickListener enterPassword = new OnClickListener() { + + @Override + public void onClick(View v) { + MucOptions muc = conversation.getMucOptions(); + String password = muc.getPassword(); + if (password == null) { + password = ""; + } + activity.quickPasswordEdit(password, new OnValueEdited() { + + @Override + public void onValueEdited(String value) { + activity.xmppConnectionService.providePasswordForMuc( + conversation, value); + } + }); + } + }; + protected ListView messagesView; + final protected List messageList = new ArrayList<>(); + protected MessageAdapter messageListAdapter; + private EditMessage mEditMessage; + private ImageButton mSendButton; + private RelativeLayout snackbar; + private TextView snackbarMessage; + private TextView snackbarAction; + private boolean messagesLoaded = true; + private Toast messageLoaderToast; + + private OnScrollListener mOnScrollListener = new OnScrollListener() { + + @Override + public void onScrollStateChanged(AbsListView view, int scrollState) { + // TODO Auto-generated method stub + + } + + @Override + public void onScroll(AbsListView view, int firstVisibleItem, + int visibleItemCount, int totalItemCount) { + synchronized (ConversationFragment.this.messageList) { + if (firstVisibleItem < 5 && messagesLoaded && messageList.size() > 0) { + long timestamp; + if (messageList.get(0).getType() == Message.TYPE_STATUS && messageList.size() >= 2) { + timestamp = messageList.get(1).getTimeSent(); + } else { + timestamp = messageList.get(0).getTimeSent(); + } + messagesLoaded = false; + activity.xmppConnectionService.loadMoreMessages(conversation, timestamp, new XmppConnectionService.OnMoreMessagesLoaded() { + @Override + public void onMoreMessagesLoaded(final int c, Conversation conversation) { + if (ConversationFragment.this.conversation != conversation) { + return; + } + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + final int oldPosition = messagesView.getFirstVisiblePosition(); + final Message message; + if (oldPosition < messageList.size()) { + message = messageList.get(oldPosition); + } else { + message = null; + } + String uuid = message != null ? message.getUuid() : null; + View v = messagesView.getChildAt(0); + final int pxOffset = (v == null) ? 0 : v.getTop(); + ConversationFragment.this.conversation.populateWithMessages(ConversationFragment.this.messageList); try { updateStatusMessages(); } catch (IllegalStateException e) { Log.d(Config.LOGTAG, "caught illegal state exception while updating status messages"); } messageListAdapter.notifyDataSetChanged(); - int pos = Math.max(getIndexOf(uuid,messageList),0); - messagesView.setSelectionFromTop(pos, pxOffset); - messagesLoaded = true; - if (messageLoaderToast != null) { - messageLoaderToast.cancel(); - } - } - }); - } - - @Override - public void informUser(final int resId) { - - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - if (messageLoaderToast != null) { - messageLoaderToast.cancel(); - } - if (ConversationFragment.this.conversation != conversation) { - return; - } - messageLoaderToast = Toast.makeText(activity, resId, Toast.LENGTH_LONG); - messageLoaderToast.show(); - } - }); - - } - }); - - } - } - } - }; - - private int getIndexOf(String uuid, List messages) { - if (uuid == null) { - return messages.size() - 1; - } - for(int i = 0; i < messages.size(); ++i) { - if (uuid.equals(messages.get(i).getUuid())) { - return i; - } else { - Message next = messages.get(i); - while(next != null && next.wasMergedIntoPrevious()) { - if (uuid.equals(next.getUuid())) { - return i; - } - next = next.next(); - } - - } - } - return -1; - } + int pos = Math.max(getIndexOf(uuid, messageList), 0); + messagesView.setSelectionFromTop(pos, pxOffset); + messagesLoaded = true; + if (messageLoaderToast != null) { + messageLoaderToast.cancel(); + } + } + }); + } + + @Override + public void informUser(final int resId) { + + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + if (messageLoaderToast != null) { + messageLoaderToast.cancel(); + } + if (ConversationFragment.this.conversation != conversation) { + return; + } + messageLoaderToast = Toast.makeText(activity, resId, Toast.LENGTH_LONG); + messageLoaderToast.show(); + } + }); + + } + }); + + } + } + } + }; + + private int getIndexOf(String uuid, List messages) { + if (uuid == null) { + return messages.size() - 1; + } + for (int i = 0; i < messages.size(); ++i) { + if (uuid.equals(messages.get(i).getUuid())) { + return i; + } else { + Message next = messages.get(i); + while (next != null && next.wasMergedIntoPrevious()) { + if (uuid.equals(next.getUuid())) { + return i; + } + next = next.next(); + } + + } + } + return -1; + } public Pair getScrollPosition() { if (this.messagesView.getCount() == 0 || @@ -243,334 +243,334 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa protected OnClickListener clickToDecryptListener = new OnClickListener() { - @Override - public void onClick(View v) { - PendingIntent pendingIntent = conversation.getAccount().getPgpDecryptionService().getPendingIntent(); - if (pendingIntent != null) { - try { - activity.startIntentSenderForResult(pendingIntent.getIntentSender(), + @Override + public void onClick(View v) { + PendingIntent pendingIntent = conversation.getAccount().getPgpDecryptionService().getPendingIntent(); + if (pendingIntent != null) { + try { + activity.startIntentSenderForResult(pendingIntent.getIntentSender(), ConversationActivity.REQUEST_DECRYPT_PGP, null, 0, 0, 0); - } catch (SendIntentException e) { - Toast.makeText(activity,R.string.unable_to_connect_to_keychain, Toast.LENGTH_SHORT).show(); - conversation.getAccount().getPgpDecryptionService().continueDecryption(true); - } - } - updateSnackBar(conversation); - } - }; - protected OnClickListener clickToVerify = new OnClickListener() { - - @Override - public void onClick(View v) { - activity.verifyOtrSessionDialog(conversation, v); - } - }; - private OnEditorActionListener mEditorActionListener = new OnEditorActionListener() { - - @Override - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - if (actionId == EditorInfo.IME_ACTION_SEND) { - InputMethodManager imm = (InputMethodManager) v.getContext() - .getSystemService(Context.INPUT_METHOD_SERVICE); - if (imm.isFullscreenMode()) { - imm.hideSoftInputFromWindow(v.getWindowToken(), 0); - } - sendMessage(); - return true; - } else { - return false; - } - } - }; - private OnClickListener mSendButtonListener = new OnClickListener() { - - @Override - public void onClick(View v) { - Object tag = v.getTag(); - if (tag instanceof SendButtonAction) { - SendButtonAction action = (SendButtonAction) tag; - switch (action) { - case TAKE_PHOTO: - activity.attachFile(ConversationActivity.ATTACHMENT_CHOICE_TAKE_PHOTO); - break; - case SEND_LOCATION: - activity.attachFile(ConversationActivity.ATTACHMENT_CHOICE_LOCATION); - break; - case RECORD_VOICE: - activity.attachFile(ConversationActivity.ATTACHMENT_CHOICE_RECORD_VOICE); - break; - case CHOOSE_PICTURE: - activity.attachFile(ConversationActivity.ATTACHMENT_CHOICE_CHOOSE_IMAGE); - break; - case CANCEL: - if (conversation != null) { - if (conversation.getCorrectingMessage() != null) { - conversation.setCorrectingMessage(null); - mEditMessage.getEditableText().clear(); - } - if (conversation.getMode() == Conversation.MODE_MULTI) { - conversation.setNextCounterpart(null); - } - updateChatMsgHint(); - updateSendButton(); - } - break; - default: - sendMessage(); - } - } else { - sendMessage(); - } - } - }; - private View.OnLongClickListener mSendButtonLongListener = new View.OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - final String body = mEditMessage.getText().toString(); - if (body.length() == 0) { - mEditMessage.getText().insert(0, "/me "); - } - return true; - } - }; - private OnClickListener clickToMuc = new OnClickListener() { - - @Override - public void onClick(View v) { - Intent intent = new Intent(getActivity(), ConferenceDetailsActivity.class); - intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC); - intent.putExtra("uuid", conversation.getUuid()); - startActivity(intent); - } - }; - private ConversationActivity activity; - private Message selectedMessage; - - public void setMessagesLoaded() { - this.messagesLoaded = true; - } - - private void sendMessage() { - final String body = mEditMessage.getText().toString(); - if (body.length() == 0 || this.conversation == null) { - return; - } - final Message message; - if (conversation.getCorrectingMessage() == null) { - message = new Message(conversation, body, conversation.getNextEncryption()); - if (conversation.getMode() == Conversation.MODE_MULTI) { - if (conversation.getNextCounterpart() != null) { - message.setCounterpart(conversation.getNextCounterpart()); - message.setType(Message.TYPE_PRIVATE); - } - } - } else { - message = conversation.getCorrectingMessage(); - message.setBody(body); - message.setEdited(message.getUuid()); - message.setUuid(UUID.randomUUID().toString()); - conversation.setCorrectingMessage(null); - } - switch (conversation.getNextEncryption()) { - case Message.ENCRYPTION_OTR: - sendOtrMessage(message); - break; - case Message.ENCRYPTION_PGP: - sendPgpMessage(message); - break; - case Message.ENCRYPTION_AXOLOTL: - if(!activity.trustKeysIfNeeded(ConversationActivity.REQUEST_TRUST_KEYS_TEXT)) { - sendAxolotlMessage(message); - } - break; - default: - sendPlainTextMessage(message); - } - } - - public void updateChatMsgHint() { - final boolean multi = conversation.getMode() == Conversation.MODE_MULTI; - if (conversation.getCorrectingMessage() != null) { - this.mEditMessage.setHint(R.string.send_corrected_message); - } else if (multi && conversation.getNextCounterpart() != null) { - this.mEditMessage.setHint(getString( - R.string.send_private_message_to, - conversation.getNextCounterpart().getResourcepart())); - } else if (multi && !conversation.getMucOptions().participating()) { - this.mEditMessage.setHint(R.string.you_are_not_participating); - } else { - this.mEditMessage.setHint(UIHelper.getMessageHint(activity,conversation)); - getActivity().invalidateOptionsMenu(); - } - } - - public void setupIme() { - if (activity == null) { - return; - } else if (activity.usingEnterKey() && activity.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()) { - mEditMessage.setInputType(mEditMessage.getInputType() | InputType.TYPE_TEXT_FLAG_MULTI_LINE); - mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE)); - } else { - mEditMessage.setInputType(mEditMessage.getInputType() | InputType.TYPE_TEXT_FLAG_MULTI_LINE); - mEditMessage.setInputType(mEditMessage.getInputType() | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE); - } - } - - @Override - public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - final View view = inflater.inflate(R.layout.fragment_conversation, container, false); - view.setOnClickListener(null); - mEditMessage = (EditMessage) view.findViewById(R.id.textinput); - mEditMessage.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - if (activity != null) { - activity.hideConversationsOverview(); - } - } - }); - mEditMessage.setOnEditorActionListener(mEditorActionListener); - - mSendButton = (ImageButton) view.findViewById(R.id.textSendButton); - mSendButton.setOnClickListener(this.mSendButtonListener); - mSendButton.setOnLongClickListener(this.mSendButtonLongListener); - - snackbar = (RelativeLayout) view.findViewById(R.id.snackbar); - snackbarMessage = (TextView) view.findViewById(R.id.snackbar_message); - snackbarAction = (TextView) view.findViewById(R.id.snackbar_action); - - messagesView = (ListView) view.findViewById(R.id.messages_view); - messagesView.setOnScrollListener(mOnScrollListener); - messagesView.setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL); - messageListAdapter = new MessageAdapter((ConversationActivity) getActivity(), this.messageList); - messageListAdapter.setOnContactPictureClicked(new OnContactPictureClicked() { - - @Override - public void onContactPictureClicked(Message message) { - if (message.getStatus() <= Message.STATUS_RECEIVED) { - if (message.getConversation().getMode() == Conversation.MODE_MULTI) { - Jid user = message.getCounterpart(); - if (user != null && !user.isBareJid()) { - if (!message.getConversation().getMucOptions().isUserInRoom(user)) { - Toast.makeText(activity,activity.getString(R.string.user_has_left_conference,user.getResourcepart()),Toast.LENGTH_SHORT).show(); - } - highlightInConference(user.getResourcepart()); - } - } else { - if (!message.getContact().isSelf()) { - String fingerprint; - if (message.getEncryption() == Message.ENCRYPTION_PGP - || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { - fingerprint = "pgp"; - } else { - fingerprint = message.getFingerprint(); - } - activity.switchToContactDetails(message.getContact(), fingerprint); - } - } - } else { - Account account = message.getConversation().getAccount(); - Intent intent; - if (activity.manuallyChangePresence()) { - intent = new Intent(activity, SetPresenceActivity.class); - intent.putExtra(SetPresenceActivity.EXTRA_ACCOUNT, account.getJid().toBareJid().toString()); - } else { - intent = new Intent(activity, EditAccountActivity.class); - intent.putExtra("jid", account.getJid().toBareJid().toString()); - String fingerprint; - if (message.getEncryption() == Message.ENCRYPTION_PGP - || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { - fingerprint = "pgp"; - } else { - fingerprint = message.getFingerprint(); - } - intent.putExtra("fingerprint", fingerprint); - } - startActivity(intent); - } - } - }); - messageListAdapter - .setOnContactPictureLongClicked(new OnContactPictureLongClicked() { - - @Override - public void onContactPictureLongClicked(Message message) { - if (message.getStatus() <= Message.STATUS_RECEIVED) { - if (message.getConversation().getMode() == Conversation.MODE_MULTI) { - Jid user = message.getCounterpart(); - if (user != null && !user.isBareJid()) { - if (message.getConversation().getMucOptions().isUserInRoom(user)) { - privateMessageWith(user); - } else { - Toast.makeText(activity, activity.getString(R.string.user_has_left_conference, user.getResourcepart()), Toast.LENGTH_SHORT).show(); - } - } - } - } else { - activity.showQrCode(); - } - } - }); - messagesView.setAdapter(messageListAdapter); - - registerForContextMenu(messagesView); - - return view; - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - synchronized (this.messageList) { - super.onCreateContextMenu(menu, v, menuInfo); - AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo; - this.selectedMessage = this.messageList.get(acmi.position); - populateContextMenu(menu); - } - } - - private void populateContextMenu(ContextMenu menu) { - final Message m = this.selectedMessage; - final Transferable t = m.getTransferable(); - Message relevantForCorrection = m; - while(relevantForCorrection.mergeable(relevantForCorrection.next())) { - relevantForCorrection = relevantForCorrection.next(); - } - if (m.getType() != Message.TYPE_STATUS) { - final boolean treatAsFile = m.getType() != Message.TYPE_TEXT - && m.getType() != Message.TYPE_PRIVATE - && t == null; - activity.getMenuInflater().inflate(R.menu.message_context, menu); - menu.setHeaderTitle(R.string.message_options); - MenuItem copyText = menu.findItem(R.id.copy_text); - MenuItem selectText = menu.findItem(R.id.select_text); - MenuItem retryDecryption = menu.findItem(R.id.retry_decryption); - MenuItem correctMessage = menu.findItem(R.id.correct_message); - MenuItem shareWith = menu.findItem(R.id.share_with); - MenuItem sendAgain = menu.findItem(R.id.send_again); - MenuItem copyUrl = menu.findItem(R.id.copy_url); - MenuItem downloadFile = menu.findItem(R.id.download_file); - MenuItem cancelTransmission = menu.findItem(R.id.cancel_transmission); - MenuItem deleteFile = menu.findItem(R.id.delete_file); + } catch (SendIntentException e) { + Toast.makeText(activity, R.string.unable_to_connect_to_keychain, Toast.LENGTH_SHORT).show(); + conversation.getAccount().getPgpDecryptionService().continueDecryption(true); + } + } + updateSnackBar(conversation); + } + }; + protected OnClickListener clickToVerify = new OnClickListener() { + + @Override + public void onClick(View v) { + activity.verifyOtrSessionDialog(conversation, v); + } + }; + private OnEditorActionListener mEditorActionListener = new OnEditorActionListener() { + + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (actionId == EditorInfo.IME_ACTION_SEND) { + InputMethodManager imm = (InputMethodManager) v.getContext() + .getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm.isFullscreenMode()) { + imm.hideSoftInputFromWindow(v.getWindowToken(), 0); + } + sendMessage(); + return true; + } else { + return false; + } + } + }; + private OnClickListener mSendButtonListener = new OnClickListener() { + + @Override + public void onClick(View v) { + Object tag = v.getTag(); + if (tag instanceof SendButtonAction) { + SendButtonAction action = (SendButtonAction) tag; + switch (action) { + case TAKE_PHOTO: + activity.attachFile(ConversationActivity.ATTACHMENT_CHOICE_TAKE_PHOTO); + break; + case SEND_LOCATION: + activity.attachFile(ConversationActivity.ATTACHMENT_CHOICE_LOCATION); + break; + case RECORD_VOICE: + activity.attachFile(ConversationActivity.ATTACHMENT_CHOICE_RECORD_VOICE); + break; + case CHOOSE_PICTURE: + activity.attachFile(ConversationActivity.ATTACHMENT_CHOICE_CHOOSE_IMAGE); + break; + case CANCEL: + if (conversation != null) { + if (conversation.getCorrectingMessage() != null) { + conversation.setCorrectingMessage(null); + mEditMessage.getEditableText().clear(); + } + if (conversation.getMode() == Conversation.MODE_MULTI) { + conversation.setNextCounterpart(null); + } + updateChatMsgHint(); + updateSendButton(); + } + break; + default: + sendMessage(); + } + } else { + sendMessage(); + } + } + }; + private View.OnLongClickListener mSendButtonLongListener = new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + final String body = mEditMessage.getText().toString(); + if (body.length() == 0) { + mEditMessage.getText().insert(0, "/me "); + } + return true; + } + }; + private OnClickListener clickToMuc = new OnClickListener() { + + @Override + public void onClick(View v) { + Intent intent = new Intent(getActivity(), ConferenceDetailsActivity.class); + intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC); + intent.putExtra("uuid", conversation.getUuid()); + startActivity(intent); + } + }; + private ConversationActivity activity; + private Message selectedMessage; + + public void setMessagesLoaded() { + this.messagesLoaded = true; + } + + private void sendMessage() { + final String body = mEditMessage.getText().toString(); + if (body.length() == 0 || this.conversation == null) { + return; + } + final Message message; + if (conversation.getCorrectingMessage() == null) { + message = new Message(conversation, body, conversation.getNextEncryption()); + if (conversation.getMode() == Conversation.MODE_MULTI) { + if (conversation.getNextCounterpart() != null) { + message.setCounterpart(conversation.getNextCounterpart()); + message.setType(Message.TYPE_PRIVATE); + } + } + } else { + message = conversation.getCorrectingMessage(); + message.setBody(body); + message.setEdited(message.getUuid()); + message.setUuid(UUID.randomUUID().toString()); + conversation.setCorrectingMessage(null); + } + switch (conversation.getNextEncryption()) { + case Message.ENCRYPTION_OTR: + sendOtrMessage(message); + break; + case Message.ENCRYPTION_PGP: + sendPgpMessage(message); + break; + case Message.ENCRYPTION_AXOLOTL: + if (!activity.trustKeysIfNeeded(ConversationActivity.REQUEST_TRUST_KEYS_TEXT)) { + sendAxolotlMessage(message); + } + break; + default: + sendPlainTextMessage(message); + } + } + + public void updateChatMsgHint() { + final boolean multi = conversation.getMode() == Conversation.MODE_MULTI; + if (conversation.getCorrectingMessage() != null) { + this.mEditMessage.setHint(R.string.send_corrected_message); + } else if (multi && conversation.getNextCounterpart() != null) { + this.mEditMessage.setHint(getString( + R.string.send_private_message_to, + conversation.getNextCounterpart().getResourcepart())); + } else if (multi && !conversation.getMucOptions().participating()) { + this.mEditMessage.setHint(R.string.you_are_not_participating); + } else { + this.mEditMessage.setHint(UIHelper.getMessageHint(activity, conversation)); + getActivity().invalidateOptionsMenu(); + } + } + + public void setupIme() { + if (activity == null) { + return; + } else if (activity.usingEnterKey() && activity.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()) { + mEditMessage.setInputType(mEditMessage.getInputType() | InputType.TYPE_TEXT_FLAG_MULTI_LINE); + mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE)); + } else { + mEditMessage.setInputType(mEditMessage.getInputType() | InputType.TYPE_TEXT_FLAG_MULTI_LINE); + mEditMessage.setInputType(mEditMessage.getInputType() | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE); + } + } + + @Override + public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + final View view = inflater.inflate(R.layout.fragment_conversation, container, false); + view.setOnClickListener(null); + mEditMessage = (EditMessage) view.findViewById(R.id.textinput); + mEditMessage.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + if (activity != null) { + activity.hideConversationsOverview(); + } + } + }); + mEditMessage.setOnEditorActionListener(mEditorActionListener); + + mSendButton = (ImageButton) view.findViewById(R.id.textSendButton); + mSendButton.setOnClickListener(this.mSendButtonListener); + mSendButton.setOnLongClickListener(this.mSendButtonLongListener); + + snackbar = (RelativeLayout) view.findViewById(R.id.snackbar); + snackbarMessage = (TextView) view.findViewById(R.id.snackbar_message); + snackbarAction = (TextView) view.findViewById(R.id.snackbar_action); + + messagesView = (ListView) view.findViewById(R.id.messages_view); + messagesView.setOnScrollListener(mOnScrollListener); + messagesView.setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL); + messageListAdapter = new MessageAdapter((ConversationActivity) getActivity(), this.messageList); + messageListAdapter.setOnContactPictureClicked(new OnContactPictureClicked() { + + @Override + public void onContactPictureClicked(Message message) { + if (message.getStatus() <= Message.STATUS_RECEIVED) { + if (message.getConversation().getMode() == Conversation.MODE_MULTI) { + Jid user = message.getCounterpart(); + if (user != null && !user.isBareJid()) { + if (!message.getConversation().getMucOptions().isUserInRoom(user)) { + Toast.makeText(activity, activity.getString(R.string.user_has_left_conference, user.getResourcepart()), Toast.LENGTH_SHORT).show(); + } + highlightInConference(user.getResourcepart()); + } + } else { + if (!message.getContact().isSelf()) { + String fingerprint; + if (message.getEncryption() == Message.ENCRYPTION_PGP + || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { + fingerprint = "pgp"; + } else { + fingerprint = message.getFingerprint(); + } + activity.switchToContactDetails(message.getContact(), fingerprint); + } + } + } else { + Account account = message.getConversation().getAccount(); + Intent intent; + if (activity.manuallyChangePresence()) { + intent = new Intent(activity, SetPresenceActivity.class); + intent.putExtra(SetPresenceActivity.EXTRA_ACCOUNT, account.getJid().toBareJid().toString()); + } else { + intent = new Intent(activity, EditAccountActivity.class); + intent.putExtra("jid", account.getJid().toBareJid().toString()); + String fingerprint; + if (message.getEncryption() == Message.ENCRYPTION_PGP + || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { + fingerprint = "pgp"; + } else { + fingerprint = message.getFingerprint(); + } + intent.putExtra("fingerprint", fingerprint); + } + startActivity(intent); + } + } + }); + messageListAdapter + .setOnContactPictureLongClicked(new OnContactPictureLongClicked() { + + @Override + public void onContactPictureLongClicked(Message message) { + if (message.getStatus() <= Message.STATUS_RECEIVED) { + if (message.getConversation().getMode() == Conversation.MODE_MULTI) { + Jid user = message.getCounterpart(); + if (user != null && !user.isBareJid()) { + if (message.getConversation().getMucOptions().isUserInRoom(user)) { + privateMessageWith(user); + } else { + Toast.makeText(activity, activity.getString(R.string.user_has_left_conference, user.getResourcepart()), Toast.LENGTH_SHORT).show(); + } + } + } + } else { + activity.showQrCode(); + } + } + }); + messagesView.setAdapter(messageListAdapter); + + registerForContextMenu(messagesView); + + return view; + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + synchronized (this.messageList) { + super.onCreateContextMenu(menu, v, menuInfo); + AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo; + this.selectedMessage = this.messageList.get(acmi.position); + populateContextMenu(menu); + } + } + + private void populateContextMenu(ContextMenu menu) { + final Message m = this.selectedMessage; + final Transferable t = m.getTransferable(); + Message relevantForCorrection = m; + while (relevantForCorrection.mergeable(relevantForCorrection.next())) { + relevantForCorrection = relevantForCorrection.next(); + } + if (m.getType() != Message.TYPE_STATUS) { + final boolean treatAsFile = m.getType() != Message.TYPE_TEXT + && m.getType() != Message.TYPE_PRIVATE + && t == null; + activity.getMenuInflater().inflate(R.menu.message_context, menu); + menu.setHeaderTitle(R.string.message_options); + MenuItem copyText = menu.findItem(R.id.copy_text); + MenuItem selectText = menu.findItem(R.id.select_text); + MenuItem retryDecryption = menu.findItem(R.id.retry_decryption); + MenuItem correctMessage = menu.findItem(R.id.correct_message); + MenuItem shareWith = menu.findItem(R.id.share_with); + MenuItem sendAgain = menu.findItem(R.id.send_again); + MenuItem copyUrl = menu.findItem(R.id.copy_url); + MenuItem downloadFile = menu.findItem(R.id.download_file); + MenuItem cancelTransmission = menu.findItem(R.id.cancel_transmission); + MenuItem deleteFile = menu.findItem(R.id.delete_file); MenuItem showErrorMessage = menu.findItem(R.id.show_error_message); - if (!treatAsFile - && !GeoHelper.isGeoUri(m.getBody()) + if (!treatAsFile + && !GeoHelper.isGeoUri(m.getBody()) && !XmppUri.isXmppUri(m.getBody()) - && m.treatAsDownloadable() != Message.Decision.MUST) { - copyText.setVisible(true); + && m.treatAsDownloadable() != Message.Decision.MUST) { + copyText.setVisible(true); selectText.setVisible(ListSelectionManager.isSupported()); - } - if (m.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { - retryDecryption.setVisible(true); - } - if (((relevantForCorrection.getType() == Message.TYPE_TEXT + } + if (m.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { + retryDecryption.setVisible(true); + } + if (((relevantForCorrection.getType() == Message.TYPE_TEXT && relevantForCorrection.isLastCorrectableMessage() && conversation.getMode() == Conversation.MODE_SINGLE) || (relevantForCorrection.getType() == Message.TYPE_TEXT @@ -578,129 +578,129 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa && conversation.getMode() == Conversation.MODE_MULTI && m.getConversation().getMucOptions().nonanonymous())) && !(GeoHelper.isGeoUri(m.getBody()) || XmppUri.isXmppUri(m.getBody()))) { - correctMessage.setVisible(true); - } - if (treatAsFile + correctMessage.setVisible(true); + } + if (treatAsFile || GeoHelper.isGeoUri(m.getBody()) || XmppUri.isXmppUri(m.getBody())) { - shareWith.setVisible(true); - } - if (m.getStatus() == Message.STATUS_SEND_FAILED) { - sendAgain.setVisible(true); - } - if (m.hasFileOnRemoteHost() - || GeoHelper.isGeoUri(m.getBody()) + shareWith.setVisible(true); + } + if (m.getStatus() == Message.STATUS_SEND_FAILED) { + sendAgain.setVisible(true); + } + if (m.hasFileOnRemoteHost() + || GeoHelper.isGeoUri(m.getBody()) || XmppUri.isXmppUri(m.getBody()) - || m.treatAsDownloadable() == Message.Decision.MUST - || (t != null && t instanceof HttpDownloadConnection)) { - copyUrl.setVisible(true); - } - if ((m.getType() == Message.TYPE_TEXT && t == null && m.treatAsDownloadable() != Message.Decision.NEVER) - || (m.isFileOrImage() && t instanceof TransferablePlaceholder && m.hasFileOnRemoteHost())){ - downloadFile.setVisible(true); - downloadFile.setTitle(activity.getString(R.string.download_x_file,UIHelper.getFileDescriptionString(activity, m))); - } + || m.treatAsDownloadable() == Message.Decision.MUST + || (t != null && t instanceof HttpDownloadConnection)) { + copyUrl.setVisible(true); + } + if ((m.getType() == Message.TYPE_TEXT && t == null && m.treatAsDownloadable() != Message.Decision.NEVER) + || (m.isFileOrImage() && t instanceof TransferablePlaceholder && m.hasFileOnRemoteHost())) { + downloadFile.setVisible(true); + downloadFile.setTitle(activity.getString(R.string.download_x_file, UIHelper.getFileDescriptionString(activity, m))); + } boolean waitingOfferedSending = m.getStatus() == Message.STATUS_WAITING || m.getStatus() == Message.STATUS_UNSEND || m.getStatus() == Message.STATUS_OFFERED; if ((t != null && !(t instanceof TransferablePlaceholder)) || waitingOfferedSending && m.needsUploading()) { - cancelTransmission.setVisible(true); - } - if (treatAsFile) { + cancelTransmission.setVisible(true); + } + if (treatAsFile) { String path = m.getRelativeFilePath(); Log.d(Config.LOGTAG, "Path = " + path); if (path == null || !path.startsWith("/") || path.contains(FileBackend.getConversationsDirectory())) { deleteFile.setVisible(true); deleteFile.setTitle(activity.getString(R.string.delete_x_file, UIHelper.getFileDescriptionString(activity, m))); } - } + } if (m.getStatus() == Message.STATUS_SEND_FAILED && m.getErrorMessage() != null) { showErrorMessage.setVisible(true); } - } - } - - @Override - public boolean onContextItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.share_with: - shareWith(selectedMessage); - return true; - case R.id.copy_text: - copyText(selectedMessage); - return true; + } + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.share_with: + shareWith(selectedMessage); + return true; + case R.id.copy_text: + copyText(selectedMessage); + return true; case R.id.select_text: selectText(selectedMessage); return true; - case R.id.correct_message: - correctMessage(selectedMessage); - return true; - case R.id.send_again: - resendMessage(selectedMessage); - return true; - case R.id.copy_url: - copyUrl(selectedMessage); - return true; - case R.id.download_file: - downloadFile(selectedMessage); - return true; - case R.id.cancel_transmission: - cancelTransmission(selectedMessage); - return true; - case R.id.retry_decryption: - retryDecryption(selectedMessage); - return true; - case R.id.delete_file: - deleteFile(selectedMessage); - return true; + case R.id.correct_message: + correctMessage(selectedMessage); + return true; + case R.id.send_again: + resendMessage(selectedMessage); + return true; + case R.id.copy_url: + copyUrl(selectedMessage); + return true; + case R.id.download_file: + downloadFile(selectedMessage); + return true; + case R.id.cancel_transmission: + cancelTransmission(selectedMessage); + return true; + case R.id.retry_decryption: + retryDecryption(selectedMessage); + return true; + case R.id.delete_file: + deleteFile(selectedMessage); + return true; case R.id.show_error_message: showErrorMessage(selectedMessage); return true; - default: - return super.onContextItemSelected(item); - } - } + default: + return super.onContextItemSelected(item); + } + } private void showErrorMessage(final Message message) { AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(R.string.error_message); builder.setMessage(message.getErrorMessage()); - builder.setPositiveButton(R.string.ok,null); + builder.setPositiveButton(R.string.ok, null); builder.create().show(); } - private void shareWith(Message message) { - Intent shareIntent = new Intent(); - shareIntent.setAction(Intent.ACTION_SEND); - if (GeoHelper.isGeoUri(message.getBody())) { - shareIntent.putExtra(Intent.EXTRA_TEXT, message.getBody()); - shareIntent.setType("text/plain"); - } else { - shareIntent.putExtra(Intent.EXTRA_STREAM, - activity.xmppConnectionService.getFileBackend() - .getJingleFileUri(message)); - shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - String mime = message.getMimeType(); - if (mime == null) { - mime = "*/*"; - } - shareIntent.setType(mime); - } - try { - activity.startActivity(Intent.createChooser(shareIntent, getText(R.string.share_with))); - } catch (ActivityNotFoundException e) { - //This should happen only on faulty androids because normally chooser is always available - Toast.makeText(activity,R.string.no_application_found_to_open_file,Toast.LENGTH_SHORT).show(); - } - } - - private void copyText(Message message) { + private void shareWith(Message message) { + Intent shareIntent = new Intent(); + shareIntent.setAction(Intent.ACTION_SEND); + if (GeoHelper.isGeoUri(message.getBody())) { + shareIntent.putExtra(Intent.EXTRA_TEXT, message.getBody()); + shareIntent.setType("text/plain"); + } else { + shareIntent.putExtra(Intent.EXTRA_STREAM, + activity.xmppConnectionService.getFileBackend() + .getJingleFileUri(message)); + shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + String mime = message.getMimeType(); + if (mime == null) { + mime = "*/*"; + } + shareIntent.setType(mime); + } + try { + activity.startActivity(Intent.createChooser(shareIntent, getText(R.string.share_with))); + } catch (ActivityNotFoundException e) { + //This should happen only on faulty androids because normally chooser is always available + Toast.makeText(activity, R.string.no_application_found_to_open_file, Toast.LENGTH_SHORT).show(); + } + } + + private void copyText(Message message) { if (activity.copyTextToClipboard(message.getMergedBody().toString(), - R.string.message_text)) { - Toast.makeText(activity, R.string.message_copied_to_clipboard, - Toast.LENGTH_SHORT).show(); - } - } + R.string.message_text)) { + Toast.makeText(activity, R.string.message_copied_to_clipboard, + Toast.LENGTH_SHORT).show(); + } + } private void selectText(Message message) { final int index; @@ -721,779 +721,779 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } private void deleteFile(Message message) { - if (activity.xmppConnectionService.getFileBackend().deleteFile(message)) { - message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); - activity.updateConversationList(); - updateMessages(); - } - } - - private void resendMessage(Message message) { - if (message.getType() == Message.TYPE_FILE || message.getType() == Message.TYPE_IMAGE) { - DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message); - if (!file.exists()) { - Toast.makeText(activity, R.string.file_deleted, Toast.LENGTH_SHORT).show(); - message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); - activity.updateConversationList(); - updateMessages(); - return; - } - } - activity.xmppConnectionService.resendFailedMessages(message); - } - - private void copyUrl(Message message) { - final String url; - final int resId; - if (GeoHelper.isGeoUri(message.getBody())) { + if (activity.xmppConnectionService.getFileBackend().deleteFile(message)) { + message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); + activity.updateConversationList(); + updateMessages(); + } + } + + private void resendMessage(Message message) { + if (message.getType() == Message.TYPE_FILE || message.getType() == Message.TYPE_IMAGE) { + DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message); + if (!file.exists()) { + Toast.makeText(activity, R.string.file_deleted, Toast.LENGTH_SHORT).show(); + message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); + activity.updateConversationList(); + updateMessages(); + return; + } + } + activity.xmppConnectionService.resendFailedMessages(message); + } + + private void copyUrl(Message message) { + final String url; + final int resId; + if (GeoHelper.isGeoUri(message.getBody())) { resId = R.string.location; url = message.getBody(); } else if (XmppUri.isXmppUri(message.getBody())) { resId = R.string.contact; url = message.getBody(); - } else if (message.hasFileOnRemoteHost()) { - resId = R.string.file_url; - url = message.getFileParams().url.toString(); - } else { - url = message.getBody().trim(); - resId = R.string.file_url; - } - if (activity.copyTextToClipboard(url, resId)) { - Toast.makeText(activity, R.string.url_copied_to_clipboard, - Toast.LENGTH_SHORT).show(); - } - } - - private void downloadFile(Message message) { - activity.xmppConnectionService.getHttpConnectionManager() - .createNewDownloadConnection(message,true); - } - - private void cancelTransmission(Message message) { - Transferable transferable = message.getTransferable(); - if (transferable != null) { - transferable.cancel(); - } else { - activity.xmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED); - } - } - - private void retryDecryption(Message message) { - message.setEncryption(Message.ENCRYPTION_PGP); - activity.updateConversationList(); - updateMessages(); - conversation.getAccount().getPgpDecryptionService().decrypt(message, false); - } - - protected void privateMessageWith(final Jid counterpart) { - this.mEditMessage.setText(""); - this.conversation.setNextCounterpart(counterpart); - updateChatMsgHint(); - updateSendButton(); - } - - private void correctMessage(Message message) { - while(message.mergeable(message.next())) { - message = message.next(); - } - this.conversation.setCorrectingMessage(message); - this.mEditMessage.getEditableText().clear(); - this.mEditMessage.getEditableText().append(message.getBody()); - - } - - protected void highlightInConference(String nick) { - String oldString = mEditMessage.getText().toString().trim(); - if (oldString.isEmpty() || mEditMessage.getSelectionStart() == 0) { - mEditMessage.getText().insert(0, nick + ": "); - } else { - if (mEditMessage.getText().charAt( - mEditMessage.getSelectionStart() - 1) != ' ') { - nick = " " + nick; - } - mEditMessage.getText().insert(mEditMessage.getSelectionStart(), - nick + " "); - } - } - - @Override - public void onStop() { - super.onStop(); - if (this.conversation != null) { - final String msg = mEditMessage.getText().toString(); - this.conversation.setNextMessage(msg); - updateChatState(this.conversation, msg); - } - } - - private void updateChatState(final Conversation conversation, final String msg) { - ChatState state = msg.length() == 0 ? Config.DEFAULT_CHATSTATE : ChatState.PAUSED; - Account.State status = conversation.getAccount().getStatus(); - if (status == Account.State.ONLINE && conversation.setOutgoingChatState(state)) { - activity.xmppConnectionService.sendChatState(conversation); - } - } - - public boolean reInit(Conversation conversation) { - if (conversation == null) { - return false; - } - this.activity = (ConversationActivity) getActivity(); - setupIme(); - if (this.conversation != null) { - final String msg = mEditMessage.getText().toString(); - this.conversation.setNextMessage(msg); - if (this.conversation != conversation) { - updateChatState(this.conversation, msg); - } - this.conversation.trim(); - } - - this.conversation = conversation; - boolean canWrite = this.conversation.getMode() == Conversation.MODE_SINGLE || this.conversation.getMucOptions().participating(); - this.mEditMessage.setEnabled(canWrite); - this.mSendButton.setEnabled(canWrite); - this.mEditMessage.setKeyboardListener(null); - this.mEditMessage.setText(""); - this.mEditMessage.append(this.conversation.getNextMessage()); - this.mEditMessage.setKeyboardListener(this); - messageListAdapter.updatePreferences(); - this.messagesView.setAdapter(messageListAdapter); - updateMessages(); - this.messagesLoaded = true; - synchronized (this.messageList) { - final Message first = conversation.getFirstUnreadMessage(); - final int bottom = Math.max(0, this.messageList.size() - 1); - final int pos; - if (first == null) { - pos = bottom; - } else { - int i = getIndexOf(first.getUuid(), this.messageList); - pos = i < 0 ? bottom : i; - } - messagesView.setSelection(pos); + } else if (message.hasFileOnRemoteHost()) { + resId = R.string.file_url; + url = message.getFileParams().url.toString(); + } else { + url = message.getBody().trim(); + resId = R.string.file_url; + } + if (activity.copyTextToClipboard(url, resId)) { + Toast.makeText(activity, R.string.url_copied_to_clipboard, + Toast.LENGTH_SHORT).show(); + } + } + + private void downloadFile(Message message) { + activity.xmppConnectionService.getHttpConnectionManager() + .createNewDownloadConnection(message, true); + } + + private void cancelTransmission(Message message) { + Transferable transferable = message.getTransferable(); + if (transferable != null) { + transferable.cancel(); + } else { + activity.xmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED); + } + } + + private void retryDecryption(Message message) { + message.setEncryption(Message.ENCRYPTION_PGP); + activity.updateConversationList(); + updateMessages(); + conversation.getAccount().getPgpDecryptionService().decrypt(message, false); + } + + protected void privateMessageWith(final Jid counterpart) { + this.mEditMessage.setText(""); + this.conversation.setNextCounterpart(counterpart); + updateChatMsgHint(); + updateSendButton(); + } + + private void correctMessage(Message message) { + while (message.mergeable(message.next())) { + message = message.next(); + } + this.conversation.setCorrectingMessage(message); + this.mEditMessage.getEditableText().clear(); + this.mEditMessage.getEditableText().append(message.getBody()); + + } + + protected void highlightInConference(String nick) { + String oldString = mEditMessage.getText().toString().trim(); + if (oldString.isEmpty() || mEditMessage.getSelectionStart() == 0) { + mEditMessage.getText().insert(0, nick + ": "); + } else { + if (mEditMessage.getText().charAt( + mEditMessage.getSelectionStart() - 1) != ' ') { + nick = " " + nick; + } + mEditMessage.getText().insert(mEditMessage.getSelectionStart(), + nick + " "); + } + } + + @Override + public void onStop() { + super.onStop(); + if (this.conversation != null) { + final String msg = mEditMessage.getText().toString(); + this.conversation.setNextMessage(msg); + updateChatState(this.conversation, msg); + } + } + + private void updateChatState(final Conversation conversation, final String msg) { + ChatState state = msg.length() == 0 ? Config.DEFAULT_CHATSTATE : ChatState.PAUSED; + Account.State status = conversation.getAccount().getStatus(); + if (status == Account.State.ONLINE && conversation.setOutgoingChatState(state)) { + activity.xmppConnectionService.sendChatState(conversation); + } + } + + public boolean reInit(Conversation conversation) { + if (conversation == null) { + return false; + } + this.activity = (ConversationActivity) getActivity(); + setupIme(); + if (this.conversation != null) { + final String msg = mEditMessage.getText().toString(); + this.conversation.setNextMessage(msg); + if (this.conversation != conversation) { + updateChatState(this.conversation, msg); + } + this.conversation.trim(); + } + + this.conversation = conversation; + boolean canWrite = this.conversation.getMode() == Conversation.MODE_SINGLE || this.conversation.getMucOptions().participating(); + this.mEditMessage.setEnabled(canWrite); + this.mSendButton.setEnabled(canWrite); + this.mEditMessage.setKeyboardListener(null); + this.mEditMessage.setText(""); + this.mEditMessage.append(this.conversation.getNextMessage()); + this.mEditMessage.setKeyboardListener(this); + messageListAdapter.updatePreferences(); + this.messagesView.setAdapter(messageListAdapter); + updateMessages(); + this.messagesLoaded = true; + synchronized (this.messageList) { + final Message first = conversation.getFirstUnreadMessage(); + final int bottom = Math.max(0, this.messageList.size() - 1); + final int pos; + if (first == null) { + pos = bottom; + } else { + int i = getIndexOf(first.getUuid(), this.messageList); + pos = i < 0 ? bottom : i; + } + messagesView.setSelection(pos); return pos == bottom; - } - } - - private OnClickListener mEnableAccountListener = new OnClickListener() { - @Override - public void onClick(View v) { - final Account account = conversation == null ? null : conversation.getAccount(); - if (account != null) { - account.setOption(Account.OPTION_DISABLED, false); - activity.xmppConnectionService.updateAccount(account); - } - } - }; - - private OnClickListener mUnblockClickListener = new OnClickListener() { - @Override - public void onClick(final View v) { - v.post(new Runnable() { - @Override - public void run() { - v.setVisibility(View.INVISIBLE); - } - }); - if (conversation.isDomainBlocked()) { - BlockContactDialog.show(activity, activity.xmppConnectionService, conversation); - } else { - activity.unblockConversation(conversation); - } - } - }; - - private OnClickListener mAddBackClickListener = new OnClickListener() { - - @Override - public void onClick(View v) { - final Contact contact = conversation == null ? null : conversation.getContact(); - if (contact != null) { - activity.xmppConnectionService.createContact(contact); - activity.switchToContactDetails(contact); - } - } - }; - - private OnClickListener mAllowPresenceSubscription = new OnClickListener() { - @Override - public void onClick(View v) { - final Contact contact = conversation == null ? null : conversation.getContact(); - if (contact != null) { - activity.xmppConnectionService.sendPresencePacket(contact.getAccount(), - activity.xmppConnectionService.getPresenceGenerator() - .sendPresenceUpdatesTo(contact)); - hideSnackbar(); - } - } - }; - - private OnClickListener mAnswerSmpClickListener = new OnClickListener() { - @Override - public void onClick(View view) { - Intent intent = new Intent(activity, VerifyOTRActivity.class); - intent.setAction(VerifyOTRActivity.ACTION_VERIFY_CONTACT); - intent.putExtra("contact", conversation.getContact().getJid().toBareJid().toString()); - intent.putExtra(VerifyOTRActivity.EXTRA_ACCOUNT, conversation.getAccount().getJid().toBareJid().toString()); - intent.putExtra("mode", VerifyOTRActivity.MODE_ANSWER_QUESTION); - startActivity(intent); - } - }; - - private void updateSnackBar(final Conversation conversation) { - final Account account = conversation.getAccount(); - final Contact contact = conversation.getContact(); - final int mode = conversation.getMode(); - if (account.getStatus() == Account.State.DISABLED) { - showSnackbar(R.string.this_account_is_disabled, R.string.enable, this.mEnableAccountListener); - } else if (conversation.isBlocked()) { - showSnackbar(R.string.contact_blocked, R.string.unblock, this.mUnblockClickListener); - } else if (!contact.showInRoster() && contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { - showSnackbar(R.string.contact_added_you, R.string.add_back, this.mAddBackClickListener); - } else if (contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { - showSnackbar(R.string.contact_asks_for_presence_subscription, R.string.allow, this.mAllowPresenceSubscription); - } else if (mode == Conversation.MODE_MULTI - && !conversation.getMucOptions().online() - && account.getStatus() == Account.State.ONLINE) { - switch (conversation.getMucOptions().getError()) { - case NICK_IN_USE: - showSnackbar(R.string.nick_in_use, R.string.edit, clickToMuc); - break; - case NO_RESPONSE: - showSnackbar(R.string.joining_conference, 0, null); - break; - case SERVER_NOT_FOUND: - showSnackbar(R.string.remote_server_not_found,R.string.leave, leaveMuc); - break; - case PASSWORD_REQUIRED: - showSnackbar(R.string.conference_requires_password, R.string.enter_password, enterPassword); - break; - case BANNED: - showSnackbar(R.string.conference_banned, R.string.leave, leaveMuc); - break; - case MEMBERS_ONLY: - showSnackbar(R.string.conference_members_only, R.string.leave, leaveMuc); - break; - case KICKED: - showSnackbar(R.string.conference_kicked, R.string.join, joinMuc); - break; - case UNKNOWN: - activity.xmppConnectionService.joinMuc(conversation); - //showSnackbar(R.string.conference_unknown_error, R.string.join, joinMuc); - break; - case SHUTDOWN: - showSnackbar(R.string.conference_shutdown, R.string.join, joinMuc); - break; - default: - break; - } - } else if (account.hasPendingPgpIntent(conversation)) { - showSnackbar(R.string.openpgp_messages_found, R.string.decrypt, clickToDecryptListener); - } else if (mode == Conversation.MODE_SINGLE - && conversation.smpRequested()) { - showSnackbar(R.string.smp_requested, R.string.verify, this.mAnswerSmpClickListener); - } else if (mode == Conversation.MODE_SINGLE - && conversation.hasValidOtrSession() - && (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) - && (!conversation.isOtrFingerprintVerified())) { - showSnackbar(R.string.unknown_otr_fingerprint, R.string.verify, clickToVerify); - } else { - hideSnackbar(); - } - } - - public void updateMessages() { - synchronized (this.messageList) { - if (getView() == null) { - return; - } - final ConversationActivity activity = (ConversationActivity) getActivity(); - if (this.conversation != null) { - conversation.populateWithMessages(ConversationFragment.this.messageList); - updateSnackBar(conversation); - updateStatusMessages(); - this.messageListAdapter.notifyDataSetChanged(); - updateChatMsgHint(); - if (!activity.isConversationsOverviewVisable() || !activity.isConversationsOverviewHideable()) { - activity.sendReadMarkerIfNecessary(conversation); - } - this.updateSendButton(); - } - } - } - - protected void messageSent() { - mEditMessage.setText(""); - updateChatMsgHint(); - new Handler().post(new Runnable() { - @Override - public void run() { - int size = messageList.size(); - messagesView.setSelection(size - 1); - } - }); - } - - public void setFocusOnInputField() { - mEditMessage.requestFocus(); - } - - enum SendButtonAction {TEXT, TAKE_PHOTO, SEND_LOCATION, RECORD_VOICE, CANCEL, CHOOSE_PICTURE} - - private int getSendButtonImageResource(SendButtonAction action, Presence.Status status) { - switch (action) { - case TEXT: - switch (status) { - case CHAT: - case ONLINE: - return R.drawable.ic_send_text_online; - case AWAY: - return R.drawable.ic_send_text_away; - case XA: - case DND: - return R.drawable.ic_send_text_dnd; - default: - return R.drawable.ic_send_text_offline; - } - case TAKE_PHOTO: - switch (status) { - case CHAT: - case ONLINE: - return R.drawable.ic_send_photo_online; - case AWAY: - return R.drawable.ic_send_photo_away; - case XA: - case DND: - return R.drawable.ic_send_photo_dnd; - default: - return R.drawable.ic_send_photo_offline; - } - case RECORD_VOICE: - switch (status) { - case CHAT: - case ONLINE: - return R.drawable.ic_send_voice_online; - case AWAY: - return R.drawable.ic_send_voice_away; - case XA: - case DND: - return R.drawable.ic_send_voice_dnd; - default: - return R.drawable.ic_send_voice_offline; - } - case SEND_LOCATION: - switch (status) { - case CHAT: - case ONLINE: - return R.drawable.ic_send_location_online; - case AWAY: - return R.drawable.ic_send_location_away; - case XA: - case DND: - return R.drawable.ic_send_location_dnd; - default: - return R.drawable.ic_send_location_offline; - } - case CANCEL: - switch (status) { - case CHAT: - case ONLINE: - return R.drawable.ic_send_cancel_online; - case AWAY: - return R.drawable.ic_send_cancel_away; - case XA: - case DND: - return R.drawable.ic_send_cancel_dnd; - default: - return R.drawable.ic_send_cancel_offline; - } - case CHOOSE_PICTURE: - switch (status) { - case CHAT: - case ONLINE: - return R.drawable.ic_send_picture_online; - case AWAY: - return R.drawable.ic_send_picture_away; - case XA: - case DND: - return R.drawable.ic_send_picture_dnd; - default: - return R.drawable.ic_send_picture_offline; - } - } - return R.drawable.ic_send_text_offline; - } - - public void updateSendButton() { - final Conversation c = this.conversation; - final SendButtonAction action; - final Presence.Status status; - final String text = this.mEditMessage == null ? "" : this.mEditMessage.getText().toString(); - final boolean empty = text.length() == 0; - final boolean conference = c.getMode() == Conversation.MODE_MULTI; - if (c.getCorrectingMessage() != null && (empty || text.equals(c.getCorrectingMessage().getBody()))) { - action = SendButtonAction.CANCEL; - } else if (conference && !c.getAccount().httpUploadAvailable()) { - if (empty && c.getNextCounterpart() != null) { - action = SendButtonAction.CANCEL; - } else { - action = SendButtonAction.TEXT; - } - } else { - if (empty) { - if (conference && c.getNextCounterpart() != null) { - action = SendButtonAction.CANCEL; - } else { - String setting = activity.getPreferences().getString("quick_action", "recent"); - if (!setting.equals("none") && UIHelper.receivedLocationQuestion(conversation.getLatestMessage())) { - setting = "location"; - } else if (setting.equals("recent")) { - setting = activity.getPreferences().getString("recently_used_quick_action", "text"); - } - switch (setting) { - case "photo": - action = SendButtonAction.TAKE_PHOTO; - break; - case "location": - action = SendButtonAction.SEND_LOCATION; - break; - case "voice": - action = SendButtonAction.RECORD_VOICE; - break; - case "picture": - action = SendButtonAction.CHOOSE_PICTURE; - break; - default: - action = SendButtonAction.TEXT; - break; - } - } - } else { - action = SendButtonAction.TEXT; - } - } - if (activity.useSendButtonToIndicateStatus() && c != null - && c.getAccount().getStatus() == Account.State.ONLINE) { - if (c.getMode() == Conversation.MODE_SINGLE) { - status = c.getContact().getShownStatus(); - } else { - status = c.getMucOptions().online() ? Presence.Status.ONLINE : Presence.Status.OFFLINE; - } - } else { - status = Presence.Status.OFFLINE; - } - this.mSendButton.setTag(action); - this.mSendButton.setImageResource(getSendButtonImageResource(action, status)); - } - - protected void updateStatusMessages() { - synchronized (this.messageList) { - if (showLoadMoreMessages(conversation)) { - this.messageList.add(0, Message.createLoadMoreMessage(conversation)); - } - if (conversation.getMode() == Conversation.MODE_SINGLE) { - ChatState state = conversation.getIncomingChatState(); - if (state == ChatState.COMPOSING) { - //this.messageList.add(Message.createStatusMessage(conversation, getString(R.string.contact_is_typing, conversation.getName()))); - } else if (state == ChatState.PAUSED) { - //this.messageList.add(Message.createStatusMessage(conversation, getString(R.string.contact_has_stopped_typing, conversation.getName()))); - } else { - for (int i = this.messageList.size() - 1; i >= 0; --i) { - if (this.messageList.get(i).getStatus() == Message.STATUS_RECEIVED) { - return; - } else { - if (this.messageList.get(i).getStatus() == Message.STATUS_SEND_DISPLAYED) { + } + } + + private OnClickListener mEnableAccountListener = new OnClickListener() { + @Override + public void onClick(View v) { + final Account account = conversation == null ? null : conversation.getAccount(); + if (account != null) { + account.setOption(Account.OPTION_DISABLED, false); + activity.xmppConnectionService.updateAccount(account); + } + } + }; + + private OnClickListener mUnblockClickListener = new OnClickListener() { + @Override + public void onClick(final View v) { + v.post(new Runnable() { + @Override + public void run() { + v.setVisibility(View.INVISIBLE); + } + }); + if (conversation.isDomainBlocked()) { + BlockContactDialog.show(activity, activity.xmppConnectionService, conversation); + } else { + activity.unblockConversation(conversation); + } + } + }; + + private OnClickListener mAddBackClickListener = new OnClickListener() { + + @Override + public void onClick(View v) { + final Contact contact = conversation == null ? null : conversation.getContact(); + if (contact != null) { + activity.xmppConnectionService.createContact(contact); + activity.switchToContactDetails(contact); + } + } + }; + + private OnClickListener mAllowPresenceSubscription = new OnClickListener() { + @Override + public void onClick(View v) { + final Contact contact = conversation == null ? null : conversation.getContact(); + if (contact != null) { + activity.xmppConnectionService.sendPresencePacket(contact.getAccount(), + activity.xmppConnectionService.getPresenceGenerator() + .sendPresenceUpdatesTo(contact)); + hideSnackbar(); + } + } + }; + + private OnClickListener mAnswerSmpClickListener = new OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(activity, VerifyOTRActivity.class); + intent.setAction(VerifyOTRActivity.ACTION_VERIFY_CONTACT); + intent.putExtra("contact", conversation.getContact().getJid().toBareJid().toString()); + intent.putExtra(VerifyOTRActivity.EXTRA_ACCOUNT, conversation.getAccount().getJid().toBareJid().toString()); + intent.putExtra("mode", VerifyOTRActivity.MODE_ANSWER_QUESTION); + startActivity(intent); + } + }; + + private void updateSnackBar(final Conversation conversation) { + final Account account = conversation.getAccount(); + final Contact contact = conversation.getContact(); + final int mode = conversation.getMode(); + if (account.getStatus() == Account.State.DISABLED) { + showSnackbar(R.string.this_account_is_disabled, R.string.enable, this.mEnableAccountListener); + } else if (conversation.isBlocked()) { + showSnackbar(R.string.contact_blocked, R.string.unblock, this.mUnblockClickListener); + } else if (!contact.showInRoster() && contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { + showSnackbar(R.string.contact_added_you, R.string.add_back, this.mAddBackClickListener); + } else if (contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { + showSnackbar(R.string.contact_asks_for_presence_subscription, R.string.allow, this.mAllowPresenceSubscription); + } else if (mode == Conversation.MODE_MULTI + && !conversation.getMucOptions().online() + && account.getStatus() == Account.State.ONLINE) { + switch (conversation.getMucOptions().getError()) { + case NICK_IN_USE: + showSnackbar(R.string.nick_in_use, R.string.edit, clickToMuc); + break; + case NO_RESPONSE: + showSnackbar(R.string.joining_conference, 0, null); + break; + case SERVER_NOT_FOUND: + showSnackbar(R.string.remote_server_not_found, R.string.leave, leaveMuc); + break; + case PASSWORD_REQUIRED: + showSnackbar(R.string.conference_requires_password, R.string.enter_password, enterPassword); + break; + case BANNED: + showSnackbar(R.string.conference_banned, R.string.leave, leaveMuc); + break; + case MEMBERS_ONLY: + showSnackbar(R.string.conference_members_only, R.string.leave, leaveMuc); + break; + case KICKED: + showSnackbar(R.string.conference_kicked, R.string.join, joinMuc); + break; + case UNKNOWN: + activity.xmppConnectionService.joinMuc(conversation); + //showSnackbar(R.string.conference_unknown_error, R.string.join, joinMuc); + break; + case SHUTDOWN: + showSnackbar(R.string.conference_shutdown, R.string.join, joinMuc); + break; + default: + break; + } + } else if (account.hasPendingPgpIntent(conversation)) { + showSnackbar(R.string.openpgp_messages_found, R.string.decrypt, clickToDecryptListener); + } else if (mode == Conversation.MODE_SINGLE + && conversation.smpRequested()) { + showSnackbar(R.string.smp_requested, R.string.verify, this.mAnswerSmpClickListener); + } else if (mode == Conversation.MODE_SINGLE + && conversation.hasValidOtrSession() + && (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) + && (!conversation.isOtrFingerprintVerified())) { + showSnackbar(R.string.unknown_otr_fingerprint, R.string.verify, clickToVerify); + } else { + hideSnackbar(); + } + } + + public void updateMessages() { + synchronized (this.messageList) { + if (getView() == null) { + return; + } + final ConversationActivity activity = (ConversationActivity) getActivity(); + if (this.conversation != null) { + conversation.populateWithMessages(ConversationFragment.this.messageList); + updateSnackBar(conversation); + updateStatusMessages(); + this.messageListAdapter.notifyDataSetChanged(); + updateChatMsgHint(); + if (!activity.isConversationsOverviewVisable() || !activity.isConversationsOverviewHideable()) { + activity.sendReadMarkerIfNecessary(conversation); + } + this.updateSendButton(); + } + } + } + + protected void messageSent() { + mEditMessage.setText(""); + updateChatMsgHint(); + new Handler().post(new Runnable() { + @Override + public void run() { + int size = messageList.size(); + messagesView.setSelection(size - 1); + } + }); + } + + public void setFocusOnInputField() { + mEditMessage.requestFocus(); + } + + enum SendButtonAction {TEXT, TAKE_PHOTO, SEND_LOCATION, RECORD_VOICE, CANCEL, CHOOSE_PICTURE} + + private int getSendButtonImageResource(SendButtonAction action, Presence.Status status) { + switch (action) { + case TEXT: + switch (status) { + case CHAT: + case ONLINE: + return R.drawable.ic_send_text_online; + case AWAY: + return R.drawable.ic_send_text_away; + case XA: + case DND: + return R.drawable.ic_send_text_dnd; + default: + return R.drawable.ic_send_text_offline; + } + case TAKE_PHOTO: + switch (status) { + case CHAT: + case ONLINE: + return R.drawable.ic_send_photo_online; + case AWAY: + return R.drawable.ic_send_photo_away; + case XA: + case DND: + return R.drawable.ic_send_photo_dnd; + default: + return R.drawable.ic_send_photo_offline; + } + case RECORD_VOICE: + switch (status) { + case CHAT: + case ONLINE: + return R.drawable.ic_send_voice_online; + case AWAY: + return R.drawable.ic_send_voice_away; + case XA: + case DND: + return R.drawable.ic_send_voice_dnd; + default: + return R.drawable.ic_send_voice_offline; + } + case SEND_LOCATION: + switch (status) { + case CHAT: + case ONLINE: + return R.drawable.ic_send_location_online; + case AWAY: + return R.drawable.ic_send_location_away; + case XA: + case DND: + return R.drawable.ic_send_location_dnd; + default: + return R.drawable.ic_send_location_offline; + } + case CANCEL: + switch (status) { + case CHAT: + case ONLINE: + return R.drawable.ic_send_cancel_online; + case AWAY: + return R.drawable.ic_send_cancel_away; + case XA: + case DND: + return R.drawable.ic_send_cancel_dnd; + default: + return R.drawable.ic_send_cancel_offline; + } + case CHOOSE_PICTURE: + switch (status) { + case CHAT: + case ONLINE: + return R.drawable.ic_send_picture_online; + case AWAY: + return R.drawable.ic_send_picture_away; + case XA: + case DND: + return R.drawable.ic_send_picture_dnd; + default: + return R.drawable.ic_send_picture_offline; + } + } + return R.drawable.ic_send_text_offline; + } + + public void updateSendButton() { + final Conversation c = this.conversation; + final SendButtonAction action; + final Presence.Status status; + final String text = this.mEditMessage == null ? "" : this.mEditMessage.getText().toString(); + final boolean empty = text.length() == 0; + final boolean conference = c.getMode() == Conversation.MODE_MULTI; + if (c.getCorrectingMessage() != null && (empty || text.equals(c.getCorrectingMessage().getBody()))) { + action = SendButtonAction.CANCEL; + } else if (conference && !c.getAccount().httpUploadAvailable()) { + if (empty && c.getNextCounterpart() != null) { + action = SendButtonAction.CANCEL; + } else { + action = SendButtonAction.TEXT; + } + } else { + if (empty) { + if (conference && c.getNextCounterpart() != null) { + action = SendButtonAction.CANCEL; + } else { + String setting = activity.getPreferences().getString("quick_action", "recent"); + if (!setting.equals("none") && UIHelper.receivedLocationQuestion(conversation.getLatestMessage())) { + setting = "location"; + } else if (setting.equals("recent")) { + setting = activity.getPreferences().getString("recently_used_quick_action", "text"); + } + switch (setting) { + case "photo": + action = SendButtonAction.TAKE_PHOTO; + break; + case "location": + action = SendButtonAction.SEND_LOCATION; + break; + case "voice": + action = SendButtonAction.RECORD_VOICE; + break; + case "picture": + action = SendButtonAction.CHOOSE_PICTURE; + break; + default: + action = SendButtonAction.TEXT; + break; + } + } + } else { + action = SendButtonAction.TEXT; + } + } + if (activity.useSendButtonToIndicateStatus() && c != null + && c.getAccount().getStatus() == Account.State.ONLINE) { + if (c.getMode() == Conversation.MODE_SINGLE) { + status = c.getContact().getShownStatus(); + } else { + status = c.getMucOptions().online() ? Presence.Status.ONLINE : Presence.Status.OFFLINE; + } + } else { + status = Presence.Status.OFFLINE; + } + this.mSendButton.setTag(action); + this.mSendButton.setImageResource(getSendButtonImageResource(action, status)); + } + + protected void updateStatusMessages() { + synchronized (this.messageList) { + if (showLoadMoreMessages(conversation)) { + this.messageList.add(0, Message.createLoadMoreMessage(conversation)); + } + if (conversation.getMode() == Conversation.MODE_SINGLE) { + ChatState state = conversation.getIncomingChatState(); + if (state == ChatState.COMPOSING) { + //this.messageList.add(Message.createStatusMessage(conversation, getString(R.string.contact_is_typing, conversation.getName()))); + } else if (state == ChatState.PAUSED) { + //this.messageList.add(Message.createStatusMessage(conversation, getString(R.string.contact_has_stopped_typing, conversation.getName()))); + } else { + for (int i = this.messageList.size() - 1; i >= 0; --i) { + if (this.messageList.get(i).getStatus() == Message.STATUS_RECEIVED) { + return; + } else { + if (this.messageList.get(i).getStatus() == Message.STATUS_SEND_DISPLAYED) { // this.messageList.add(i + 1, // Message.createStatusMessage(conversation, getString(R.string.contact_has_read_up_to_this_point, conversation.getName()))); // return; - } - } - } - } - } - } - } - - private boolean showLoadMoreMessages(final Conversation c) { - final boolean mam = hasMamSupport(c); - final MessageArchiveService service = activity.xmppConnectionService.getMessageArchiveService(); - return mam && (c.getLastClearHistory() != 0 || (c.countMessages() == 0 && c.hasMessagesLeftOnServer() && !service.queryInProgress(c))); - } - - private boolean hasMamSupport(final Conversation c) { - if (c.getMode() == Conversation.MODE_SINGLE) { - final XmppConnection connection = c.getAccount().getXmppConnection(); - return connection != null && connection.getFeatures().mam(); - } else { - return c.getMucOptions().mamSupport(); - } - } - - protected void showSnackbar(final int message, final int action, final OnClickListener clickListener) { - snackbar.setVisibility(View.VISIBLE); - snackbar.setOnClickListener(null); - snackbarMessage.setText(message); - snackbarMessage.setOnClickListener(null); - snackbarAction.setVisibility(clickListener == null ? View.GONE : View.VISIBLE); - if (action != 0) { - snackbarAction.setText(action); - } - snackbarAction.setOnClickListener(clickListener); - } - - protected void hideSnackbar() { - snackbar.setVisibility(View.GONE); - } - - protected void sendPlainTextMessage(Message message) { - ConversationActivity activity = (ConversationActivity) getActivity(); - activity.xmppConnectionService.sendMessage(message); - messageSent(); - } - - protected void sendPgpMessage(final Message message) { - final ConversationActivity activity = (ConversationActivity) getActivity(); - final XmppConnectionService xmppService = activity.xmppConnectionService; - final Contact contact = message.getConversation().getContact(); - if (!activity.hasPgp()) { - activity.showInstallPgpDialog(); - return; - } - if (conversation.getAccount().getPgpSignature() == null) { - activity.announcePgp(conversation.getAccount(), conversation, activity.onOpenPGPKeyPublished); - return; - } - if (conversation.getMode() == Conversation.MODE_SINGLE) { - if (contact.getPgpKeyId() != 0) { - xmppService.getPgpEngine().hasKey(contact, - new UiCallback() { - - @Override - public void userInputRequried(PendingIntent pi, - Contact contact) { - activity.runIntent( - pi, - ConversationActivity.REQUEST_ENCRYPT_MESSAGE); - } - - @Override - public void success(Contact contact) { - activity.encryptTextMessage(message); - } - - @Override - public void error(int error, Contact contact) { - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(activity, - R.string.unable_to_connect_to_keychain, - Toast.LENGTH_SHORT - ).show(); - } - }); - } - }); - - } else { - showNoPGPKeyDialog(false, - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, - int which) { - conversation - .setNextEncryption(Message.ENCRYPTION_NONE); + } + } + } + } + } + } + } + + private boolean showLoadMoreMessages(final Conversation c) { + final boolean mam = hasMamSupport(c); + final MessageArchiveService service = activity.xmppConnectionService.getMessageArchiveService(); + return mam && (c.getLastClearHistory() != 0 || (c.countMessages() == 0 && c.hasMessagesLeftOnServer() && !service.queryInProgress(c))); + } + + private boolean hasMamSupport(final Conversation c) { + if (c.getMode() == Conversation.MODE_SINGLE) { + final XmppConnection connection = c.getAccount().getXmppConnection(); + return connection != null && connection.getFeatures().mam(); + } else { + return c.getMucOptions().mamSupport(); + } + } + + protected void showSnackbar(final int message, final int action, final OnClickListener clickListener) { + snackbar.setVisibility(View.VISIBLE); + snackbar.setOnClickListener(null); + snackbarMessage.setText(message); + snackbarMessage.setOnClickListener(null); + snackbarAction.setVisibility(clickListener == null ? View.GONE : View.VISIBLE); + if (action != 0) { + snackbarAction.setText(action); + } + snackbarAction.setOnClickListener(clickListener); + } + + protected void hideSnackbar() { + snackbar.setVisibility(View.GONE); + } + + protected void sendPlainTextMessage(Message message) { + ConversationActivity activity = (ConversationActivity) getActivity(); + activity.xmppConnectionService.sendMessage(message); + messageSent(); + } + + protected void sendPgpMessage(final Message message) { + final ConversationActivity activity = (ConversationActivity) getActivity(); + final XmppConnectionService xmppService = activity.xmppConnectionService; + final Contact contact = message.getConversation().getContact(); + if (!activity.hasPgp()) { + activity.showInstallPgpDialog(); + return; + } + if (conversation.getAccount().getPgpSignature() == null) { + activity.announcePgp(conversation.getAccount(), conversation, activity.onOpenPGPKeyPublished); + return; + } + if (conversation.getMode() == Conversation.MODE_SINGLE) { + if (contact.getPgpKeyId() != 0) { + xmppService.getPgpEngine().hasKey(contact, + new UiCallback() { + + @Override + public void userInputRequried(PendingIntent pi, + Contact contact) { + activity.runIntent( + pi, + ConversationActivity.REQUEST_ENCRYPT_MESSAGE); + } + + @Override + public void success(Contact contact) { + activity.encryptTextMessage(message); + } + + @Override + public void error(int error, Contact contact) { + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(activity, + R.string.unable_to_connect_to_keychain, + Toast.LENGTH_SHORT + ).show(); + } + }); + } + }); + + } else { + showNoPGPKeyDialog(false, + new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, + int which) { + conversation + .setNextEncryption(Message.ENCRYPTION_NONE); xmppService.updateConversation(conversation); - message.setEncryption(Message.ENCRYPTION_NONE); - xmppService.sendMessage(message); - messageSent(); - } - }); - } - } else { - if (conversation.getMucOptions().pgpKeysInUse()) { - if (!conversation.getMucOptions().everybodyHasKeys()) { - Toast warning = Toast - .makeText(getActivity(), - R.string.missing_public_keys, - Toast.LENGTH_LONG); - warning.setGravity(Gravity.CENTER_VERTICAL, 0, 0); - warning.show(); - } - activity.encryptTextMessage(message); - } else { - showNoPGPKeyDialog(true, - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, - int which) { - conversation - .setNextEncryption(Message.ENCRYPTION_NONE); - message.setEncryption(Message.ENCRYPTION_NONE); + message.setEncryption(Message.ENCRYPTION_NONE); + xmppService.sendMessage(message); + messageSent(); + } + }); + } + } else { + if (conversation.getMucOptions().pgpKeysInUse()) { + if (!conversation.getMucOptions().everybodyHasKeys()) { + Toast warning = Toast + .makeText(getActivity(), + R.string.missing_public_keys, + Toast.LENGTH_LONG); + warning.setGravity(Gravity.CENTER_VERTICAL, 0, 0); + warning.show(); + } + activity.encryptTextMessage(message); + } else { + showNoPGPKeyDialog(true, + new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, + int which) { + conversation + .setNextEncryption(Message.ENCRYPTION_NONE); + message.setEncryption(Message.ENCRYPTION_NONE); xmppService.updateConversation(conversation); - xmppService.sendMessage(message); - messageSent(); - } - }); - } - } - } - - public void showNoPGPKeyDialog(boolean plural, - DialogInterface.OnClickListener listener) { - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - builder.setIconAttribute(android.R.attr.alertDialogIcon); - if (plural) { - builder.setTitle(getString(R.string.no_pgp_keys)); - builder.setMessage(getText(R.string.contacts_have_no_pgp_keys)); - } else { - builder.setTitle(getString(R.string.no_pgp_key)); - builder.setMessage(getText(R.string.contact_has_no_pgp_key)); - } - builder.setNegativeButton(getString(R.string.cancel), null); - builder.setPositiveButton(getString(R.string.send_unencrypted), - listener); - builder.create().show(); - } - - protected void sendAxolotlMessage(final Message message) { - final ConversationActivity activity = (ConversationActivity) getActivity(); - final XmppConnectionService xmppService = activity.xmppConnectionService; - xmppService.sendMessage(message); - messageSent(); - } - - protected void sendOtrMessage(final Message message) { - final ConversationActivity activity = (ConversationActivity) getActivity(); - final XmppConnectionService xmppService = activity.xmppConnectionService; - activity.selectPresence(message.getConversation(), - new OnPresenceSelected() { - - @Override - public void onPresenceSelected() { - message.setCounterpart(conversation.getNextCounterpart()); - xmppService.sendMessage(message); - messageSent(); - } - }); - } - - public void appendText(String text) { - if (text == null) { - return; - } - String previous = this.mEditMessage.getText().toString(); - if (previous.length() != 0 && !previous.endsWith(" ")) { - text = " " + text; - } - this.mEditMessage.append(text); - } - - @Override - public boolean onEnterPressed() { - if (activity.enterIsSend()) { - sendMessage(); - return true; - } else { - return false; - } - } - - @Override - public void onTypingStarted() { - Account.State status = conversation.getAccount().getStatus(); - if (status == Account.State.ONLINE && conversation.setOutgoingChatState(ChatState.COMPOSING)) { - activity.xmppConnectionService.sendChatState(conversation); - } - activity.hideConversationsOverview(); - updateSendButton(); - } - - @Override - public void onTypingStopped() { - Account.State status = conversation.getAccount().getStatus(); - if (status == Account.State.ONLINE && conversation.setOutgoingChatState(ChatState.PAUSED)) { - activity.xmppConnectionService.sendChatState(conversation); - } - } - - @Override - public void onTextDeleted() { - Account.State status = conversation.getAccount().getStatus(); - if (status == Account.State.ONLINE && conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) { - activity.xmppConnectionService.sendChatState(conversation); - } - updateSendButton(); - } - - @Override - public void onTextChanged() { - if (conversation != null && conversation.getCorrectingMessage() != null) { - updateSendButton(); - } - } - - private int completionIndex = 0; - private int lastCompletionLength = 0; - private String incomplete; - private int lastCompletionCursor; - private boolean firstWord = false; - - @Override - public boolean onTabPressed(boolean repeated) { - if (conversation == null || conversation.getMode() == Conversation.MODE_SINGLE) { - return false; - } - if (repeated) { - completionIndex++; - } else { - lastCompletionLength = 0; - completionIndex = 0; - final String content = mEditMessage.getText().toString(); - lastCompletionCursor = mEditMessage.getSelectionEnd(); - int start = lastCompletionCursor > 0 ? content.lastIndexOf(" ",lastCompletionCursor-1) + 1 : 0; - firstWord = start == 0; - incomplete = content.substring(start,lastCompletionCursor); - } - List completions = new ArrayList<>(); - for(MucOptions.User user : conversation.getMucOptions().getUsers()) { - String name = user.getName(); - if (name != null && name.startsWith(incomplete)) { - completions.add(name+(firstWord ? ": " : " ")); - } - } - Collections.sort(completions); - if (completions.size() > completionIndex) { - String completion = completions.get(completionIndex).substring(incomplete.length()); - mEditMessage.getEditableText().delete(lastCompletionCursor,lastCompletionCursor + lastCompletionLength); - mEditMessage.getEditableText().insert(lastCompletionCursor, completion); - lastCompletionLength = completion.length(); - } else { - completionIndex = -1; - mEditMessage.getEditableText().delete(lastCompletionCursor,lastCompletionCursor + lastCompletionLength); - lastCompletionLength = 0; - } - return true; - } - - @Override - public void onActivityResult(int requestCode, int resultCode, - final Intent data) { - if (resultCode == Activity.RESULT_OK) { - if (requestCode == ConversationActivity.REQUEST_DECRYPT_PGP) { - activity.getSelectedConversation().getAccount().getPgpDecryptionService().continueDecryption(true); - } else if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_TEXT) { - final String body = mEditMessage.getText().toString(); - Message message = new Message(conversation, body, conversation.getNextEncryption()); - sendAxolotlMessage(message); - } else if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_MENU) { - int choice = data.getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID); - activity.selectPresenceToAttachFile(choice, conversation.getNextEncryption()); - } - } - } + xmppService.sendMessage(message); + messageSent(); + } + }); + } + } + } + + public void showNoPGPKeyDialog(boolean plural, + DialogInterface.OnClickListener listener) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + if (plural) { + builder.setTitle(getString(R.string.no_pgp_keys)); + builder.setMessage(getText(R.string.contacts_have_no_pgp_keys)); + } else { + builder.setTitle(getString(R.string.no_pgp_key)); + builder.setMessage(getText(R.string.contact_has_no_pgp_key)); + } + builder.setNegativeButton(getString(R.string.cancel), null); + builder.setPositiveButton(getString(R.string.send_unencrypted), + listener); + builder.create().show(); + } + + protected void sendAxolotlMessage(final Message message) { + final ConversationActivity activity = (ConversationActivity) getActivity(); + final XmppConnectionService xmppService = activity.xmppConnectionService; + xmppService.sendMessage(message); + messageSent(); + } + + protected void sendOtrMessage(final Message message) { + final ConversationActivity activity = (ConversationActivity) getActivity(); + final XmppConnectionService xmppService = activity.xmppConnectionService; + activity.selectPresence(message.getConversation(), + new OnPresenceSelected() { + + @Override + public void onPresenceSelected() { + message.setCounterpart(conversation.getNextCounterpart()); + xmppService.sendMessage(message); + messageSent(); + } + }); + } + + public void appendText(String text) { + if (text == null) { + return; + } + String previous = this.mEditMessage.getText().toString(); + if (previous.length() != 0 && !previous.endsWith(" ")) { + text = " " + text; + } + this.mEditMessage.append(text); + } + + @Override + public boolean onEnterPressed() { + if (activity.enterIsSend()) { + sendMessage(); + return true; + } else { + return false; + } + } + + @Override + public void onTypingStarted() { + Account.State status = conversation.getAccount().getStatus(); + if (status == Account.State.ONLINE && conversation.setOutgoingChatState(ChatState.COMPOSING)) { + activity.xmppConnectionService.sendChatState(conversation); + } + activity.hideConversationsOverview(); + updateSendButton(); + } + + @Override + public void onTypingStopped() { + Account.State status = conversation.getAccount().getStatus(); + if (status == Account.State.ONLINE && conversation.setOutgoingChatState(ChatState.PAUSED)) { + activity.xmppConnectionService.sendChatState(conversation); + } + } + + @Override + public void onTextDeleted() { + Account.State status = conversation.getAccount().getStatus(); + if (status == Account.State.ONLINE && conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) { + activity.xmppConnectionService.sendChatState(conversation); + } + updateSendButton(); + } + + @Override + public void onTextChanged() { + if (conversation != null && conversation.getCorrectingMessage() != null) { + updateSendButton(); + } + } + + private int completionIndex = 0; + private int lastCompletionLength = 0; + private String incomplete; + private int lastCompletionCursor; + private boolean firstWord = false; + + @Override + public boolean onTabPressed(boolean repeated) { + if (conversation == null || conversation.getMode() == Conversation.MODE_SINGLE) { + return false; + } + if (repeated) { + completionIndex++; + } else { + lastCompletionLength = 0; + completionIndex = 0; + final String content = mEditMessage.getText().toString(); + lastCompletionCursor = mEditMessage.getSelectionEnd(); + int start = lastCompletionCursor > 0 ? content.lastIndexOf(" ", lastCompletionCursor - 1) + 1 : 0; + firstWord = start == 0; + incomplete = content.substring(start, lastCompletionCursor); + } + List completions = new ArrayList<>(); + for (MucOptions.User user : conversation.getMucOptions().getUsers()) { + String name = user.getName(); + if (name != null && name.startsWith(incomplete)) { + completions.add(name + (firstWord ? ": " : " ")); + } + } + Collections.sort(completions); + if (completions.size() > completionIndex) { + String completion = completions.get(completionIndex).substring(incomplete.length()); + mEditMessage.getEditableText().delete(lastCompletionCursor, lastCompletionCursor + lastCompletionLength); + mEditMessage.getEditableText().insert(lastCompletionCursor, completion); + lastCompletionLength = completion.length(); + } else { + completionIndex = -1; + mEditMessage.getEditableText().delete(lastCompletionCursor, lastCompletionCursor + lastCompletionLength); + lastCompletionLength = 0; + } + return true; + } + + @Override + public void onActivityResult(int requestCode, int resultCode, + final Intent data) { + if (resultCode == Activity.RESULT_OK) { + if (requestCode == ConversationActivity.REQUEST_DECRYPT_PGP) { + activity.getSelectedConversation().getAccount().getPgpDecryptionService().continueDecryption(true); + } else if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_TEXT) { + final String body = mEditMessage.getText().toString(); + Message message = new Message(conversation, body, conversation.getNextEncryption()); + sendAxolotlMessage(message); + } else if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_MENU) { + int choice = data.getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID); + activity.selectPresenceToAttachFile(choice, conversation.getNextEncryption()); + } + } + } } diff --git a/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java b/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java index b5e3c8a1c..58453ba75 100644 --- a/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java +++ b/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java @@ -61,1104 +61,1104 @@ import de.pixart.messenger.xmpp.jid.Jid; import de.pixart.messenger.xmpp.pep.Avatar; public class EditAccountActivity extends OmemoActivity implements OnAccountUpdate, - OnKeyStatusUpdated, OnCaptchaRequested, KeyChainAliasCallback, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnMamPreferencesFetched { - - private static final int REQUEST_DATA_SAVER = 0x37af244; - private AutoCompleteTextView mAccountJid; - private EditText mPassword; - private EditText mPasswordConfirm; - private CheckBox mRegisterNew; - private Button mCancelButton; - private Button mSaveButton; - private Button mDisableOsOptimizationsButton; - private TextView mDisableOsOptimizationsHeadline; - private TextView getmDisableOsOptimizationsBody; - private TableLayout mMoreTable; - - private LinearLayout mStats; - private RelativeLayout mOsOptimizations; - private TextView mServerInfoSm; - private TextView mServerInfoRosterVersion; - private TextView mServerInfoCarbons; - private TextView mServerInfoMam; - private TextView mServerInfoCSI; - private TextView mServerInfoBlocking; - private TextView mServerInfoPep; - private TextView mServerInfoHttpUpload; - private TextView mServerInfoPush; - private TextView mSessionEst; - private TextView mOtrFingerprint; - private TextView mAxolotlFingerprint; - private TextView mOwnFingerprintDesc; - private TextView mAccountJidLabel; - private ImageView mAvatar; - private RelativeLayout mOtrFingerprintBox; - private RelativeLayout mAxolotlFingerprintBox; - private ImageButton mOtrFingerprintToClipboardButton; - private ImageButton mAxolotlFingerprintToClipboardButton; - private ImageButton mRegenerateAxolotlKeyButton; - private LinearLayout keys; - private LinearLayout keysCard; - private LinearLayout mNamePort; - private EditText mHostname; - private EditText mPort; - private AlertDialog mCaptchaDialog = null; - - private Jid jidToEdit; - private boolean mInitMode = false; - private boolean mUsernameMode = Config.DOMAIN_LOCK != null; - private boolean mShowOptions = false; - private Account mAccount; - private String messageFingerprint; - - private boolean mFetchingAvatar = false; - - private final OnClickListener mSaveButtonClickListener = new OnClickListener() { - - @Override - public void onClick(final View v) { - final String password = mPassword.getText().toString(); - final String passwordConfirm = mPasswordConfirm.getText().toString(); - - if (!mInitMode && passwordChangedInMagicCreateMode()) { - gotoChangePassword(password); - return; - } - if (mInitMode && mAccount != null) { - mAccount.setOption(Account.OPTION_DISABLED, false); - } - if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED && !accountInfoEdited()) { - mAccount.setOption(Account.OPTION_DISABLED, false); - if (!xmppConnectionService.updateAccount(mAccount)) { - Toast.makeText(EditAccountActivity.this,R.string.unable_to_update_account,Toast.LENGTH_SHORT).show(); - } - return; - } - final boolean registerNewAccount = mRegisterNew.isChecked() && !Config.DISALLOW_REGISTRATION_IN_UI; - if (mUsernameMode && mAccountJid.getText().toString().contains("@")) { - mAccountJid.setError(getString(R.string.invalid_username)); - mAccountJid.requestFocus(); - return; - } - final Jid jid; - try { - if (mUsernameMode) { - jid = Jid.fromParts(mAccountJid.getText().toString(), getUserModeDomain(), null); - } else { - jid = Jid.fromString(mAccountJid.getText().toString()); - } - } catch (final InvalidJidException e) { - if (mUsernameMode) { - mAccountJid.setError(getString(R.string.invalid_username)); - } else { - mAccountJid.setError(getString(R.string.invalid_jid)); - } - mAccountJid.requestFocus(); - return; - } - String hostname = null; - int numericPort = 5222; - if (mShowOptions) { - hostname = mHostname.getText().toString().replaceAll("\\s",""); - final String port = mPort.getText().toString().replaceAll("\\s",""); - if (hostname.contains(" ")) { - mHostname.setError(getString(R.string.not_valid_hostname)); - mHostname.requestFocus(); - return; - } - try { - numericPort = Integer.parseInt(port); - if (numericPort < 0 || numericPort > 65535) { - mPort.setError(getString(R.string.not_a_valid_port)); - mPort.requestFocus(); - return; - } - - } catch (NumberFormatException e) { - mPort.setError(getString(R.string.not_a_valid_port)); - mPort.requestFocus(); - return; - } - } - - if (jid.isDomainJid()) { - if (mUsernameMode) { - mAccountJid.setError(getString(R.string.invalid_username)); - } else { - mAccountJid.setError(getString(R.string.invalid_jid)); - } - mAccountJid.requestFocus(); - return; - } - if (registerNewAccount) { - if (XmppConnection.errorMessage != null) { - Toast.makeText(EditAccountActivity.this,XmppConnection.errorMessage,Toast.LENGTH_LONG).show(); - } - if (!password.equals(passwordConfirm)) { - mPasswordConfirm.setError(getString(R.string.passwords_do_not_match)); - mPasswordConfirm.requestFocus(); - return; - } - } - if (mAccount != null) { - if (mInitMode && mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)) { - mAccount.setOption(Account.OPTION_MAGIC_CREATE, mAccount.getPassword().contains(password)); - } - mAccount.setJid(jid); - mAccount.setPort(numericPort); - mAccount.setHostname(hostname); - if (XmppConnection.errorMessage != null) { - mAccountJid.setError(XmppConnection.errorMessage); - } else { - mAccountJid.setError(null); - } - mPasswordConfirm.setError(null); - mAccount.setPassword(password); - mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount); - if (!xmppConnectionService.updateAccount(mAccount)) { - Toast.makeText(EditAccountActivity.this,R.string.unable_to_update_account,Toast.LENGTH_SHORT).show(); - return; - } - } else { - if (xmppConnectionService.findAccountByJid(jid) != null) { - mAccountJid.setError(getString(R.string.account_already_exists)); - mAccountJid.requestFocus(); - return; - } - mAccount = new Account(jid.toBareJid(), password); - mAccount.setPort(numericPort); - mAccount.setHostname(hostname); - mAccount.setOption(Account.OPTION_USETLS, true); - mAccount.setOption(Account.OPTION_USECOMPRESSION, true); - mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount); - xmppConnectionService.createAccount(mAccount); - } - mHostname.setError(null); - mPort.setError(null); - if (!mAccount.isOptionSet(Account.OPTION_DISABLED) - && !registerNewAccount - && !mInitMode) { - finish(); - } else { - updateSaveButton(); - updateAccountInformation(true); - } - - } - }; - private final OnClickListener mCancelButtonClickListener = new OnClickListener() { - - @Override - public void onClick(final View v) { - deleteMagicCreatedAccountAndReturnIfNecessary(); - finish(); - } - }; - private Toast mFetchingMamPrefsToast; - private TableRow mPushRow; - private String mSavedInstanceAccount; - private boolean mSavedInstanceInit = false; - - public void refreshUiReal() { - invalidateOptionsMenu(); - if (mAccount != null - && mAccount.getStatus() != Account.State.ONLINE - && mFetchingAvatar) { - startActivity(new Intent(getApplicationContext(), - ManageAccountActivity.class)); - finish(); - } else if (mInitMode && mAccount != null && mAccount.getStatus() == Account.State.ONLINE) { - if (!mFetchingAvatar) { - mFetchingAvatar = true; - xmppConnectionService.checkForAvatar(mAccount, mAvatarFetchCallback); - } - } - if (mAccount != null) { - updateAccountInformation(false); - } - updateSaveButton(); - } - - @Override - public boolean onNavigateUp() { - deleteMagicCreatedAccountAndReturnIfNecessary(); - return super.onNavigateUp(); - } - - @Override - public void onBackPressed() { - deleteMagicCreatedAccountAndReturnIfNecessary(); - super.onBackPressed(); - } - - private void deleteMagicCreatedAccountAndReturnIfNecessary() { - if (Config.MAGIC_CREATE_DOMAIN != null - && mAccount != null - && mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) - && mAccount.isOptionSet(Account.OPTION_REGISTER) - && xmppConnectionService.getAccounts().size() == 1) { - xmppConnectionService.deleteAccount(mAccount); - startActivity(new Intent(EditAccountActivity.this, WelcomeActivity.class)); - } - } - - @Override - public void onAccountUpdate() { - refreshUi(); - } - - private final UiCallback mAvatarFetchCallback = new UiCallback() { - - @Override - public void userInputRequried(final PendingIntent pi, final Avatar avatar) { - finishInitialSetup(avatar); - } - - @Override - public void success(final Avatar avatar) { - finishInitialSetup(avatar); - } - - @Override - public void error(final int errorCode, final Avatar avatar) { - finishInitialSetup(avatar); - } - }; - private final TextWatcher mTextWatcher = new TextWatcher() { - - @Override - public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { - updateSaveButton(); - } - - @Override - public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { - } - - @Override - public void afterTextChanged(final Editable s) { - - } - }; - - private final OnClickListener mAvatarClickListener = new OnClickListener() { - @Override - public void onClick(final View view) { - if (mAccount != null) { - final Intent intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class); - intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toBareJid().toString()); - startActivity(intent); - } - } - }; - - protected void finishInitialSetup(final Avatar avatar) { - runOnUiThread(new Runnable() { - - @Override - public void run() { - final Intent intent; - final XmppConnection connection = mAccount.getXmppConnection(); - final boolean wasFirstAccount = xmppConnectionService != null && xmppConnectionService.getAccounts().size() == 1; - if (avatar != null || (connection != null && !connection.getFeatures().pep())) { - intent = new Intent(getApplicationContext(), StartConversationActivity.class); - if (wasFirstAccount) { - intent.putExtra("init", true); - } - } else { - intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class); - intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toBareJid().toString()); - intent.putExtra("setup", true); - } - if (wasFirstAccount) { - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - } - startActivity(intent); - finish(); - } - }); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (requestCode == REQUEST_BATTERY_OP || requestCode == REQUEST_DATA_SAVER) { - updateAccountInformation(mAccount == null); - } - } - - protected void updateSaveButton() { - boolean accountInfoEdited = accountInfoEdited(); - - if (!mInitMode && passwordChangedInMagicCreateMode()) { - this.mSaveButton.setText(R.string.change_password); - this.mSaveButton.setEnabled(true); - this.mSaveButton.setTextColor(getPrimaryTextColor()); - } else if (accountInfoEdited && !mInitMode) { - this.mSaveButton.setText(R.string.save); - this.mSaveButton.setEnabled(true); - this.mSaveButton.setTextColor(getPrimaryTextColor()); - } else if (mAccount != null - && (mAccount.getStatus() == Account.State.CONNECTING || mAccount.getStatus() == Account.State.REGISTRATION_SUCCESSFUL|| mFetchingAvatar)) { - this.mSaveButton.setEnabled(false); - this.mSaveButton.setTextColor(getSecondaryTextColor()); - this.mSaveButton.setText(R.string.account_status_connecting); - } else if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED && !mInitMode) { - this.mSaveButton.setEnabled(true); - this.mSaveButton.setTextColor(getPrimaryTextColor()); - this.mSaveButton.setText(R.string.enable); - } else { - this.mSaveButton.setEnabled(true); - this.mSaveButton.setTextColor(getPrimaryTextColor()); - if (!mInitMode) { - if (mAccount != null && mAccount.isOnlineAndConnected()) { - this.mSaveButton.setText(R.string.save); - if (!accountInfoEdited) { - this.mSaveButton.setEnabled(false); - this.mSaveButton.setTextColor(getSecondaryTextColor()); - } - } else { - this.mSaveButton.setText(R.string.connect); - } - } else { - this.mSaveButton.setText(R.string.next); - } - } - } - - protected boolean accountInfoEdited() { - if (this.mAccount == null) { - return false; - } - return jidEdited() || - !this.mAccount.getPassword().equals(this.mPassword.getText().toString()) || - !this.mAccount.getHostname().equals(this.mHostname.getText().toString()) || - !String.valueOf(this.mAccount.getPort()).equals(this.mPort.getText().toString()); - } - - protected boolean jidEdited() { - final String unmodified; - if (mUsernameMode) { - unmodified = this.mAccount.getJid().getLocalpart(); - } else { - unmodified = this.mAccount.getJid().toBareJid().toString(); - } - return !unmodified.equals(this.mAccountJid.getText().toString()); - } - - protected boolean passwordChangedInMagicCreateMode() { - return mAccount != null - && mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) - && !this.mAccount.getPassword().equals(this.mPassword.getText().toString()) - && !this.jidEdited() - && mAccount.isOnlineAndConnected(); - } - - @Override - protected String getShareableUri() { - if (mAccount != null) { - return mAccount.getShareableUri(); - } else { - return ""; - } - } - - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (savedInstanceState != null) { - this.mSavedInstanceAccount = savedInstanceState.getString("account"); - this.mSavedInstanceInit = savedInstanceState.getBoolean("initMode", false); - } - setContentView(R.layout.activity_edit_account); - this.mAccountJid = (AutoCompleteTextView) findViewById(R.id.account_jid); - this.mAccountJid.addTextChangedListener(this.mTextWatcher); - this.mAccountJidLabel = (TextView) findViewById(R.id.account_jid_label); - this.mPassword = (EditText) findViewById(R.id.account_password); - this.mPassword.addTextChangedListener(this.mTextWatcher); - this.mPasswordConfirm = (EditText) findViewById(R.id.account_password_confirm); - this.mAvatar = (ImageView) findViewById(R.id.avater); - this.mAvatar.setOnClickListener(this.mAvatarClickListener); - this.mRegisterNew = (CheckBox) findViewById(R.id.account_register_new); - this.mStats = (LinearLayout) findViewById(R.id.stats); - this.mOsOptimizations = (RelativeLayout) findViewById(R.id.os_optimization); - this.mDisableOsOptimizationsButton = (Button) findViewById(R.id.os_optimization_disable); - this.mDisableOsOptimizationsHeadline = (TextView) findViewById(R.id.os_optimization_headline); - this.getmDisableOsOptimizationsBody = (TextView) findViewById(R.id.os_optimization_body); - this.mSessionEst = (TextView) findViewById(R.id.session_est); - this.mServerInfoRosterVersion = (TextView) findViewById(R.id.server_info_roster_version); - this.mServerInfoCarbons = (TextView) findViewById(R.id.server_info_carbons); - this.mServerInfoMam = (TextView) findViewById(R.id.server_info_mam); - this.mServerInfoCSI = (TextView) findViewById(R.id.server_info_csi); - this.mServerInfoBlocking = (TextView) findViewById(R.id.server_info_blocking); - this.mServerInfoSm = (TextView) findViewById(R.id.server_info_sm); - this.mServerInfoPep = (TextView) findViewById(R.id.server_info_pep); - this.mServerInfoHttpUpload = (TextView) findViewById(R.id.server_info_http_upload); - this.mPushRow = (TableRow) findViewById(R.id.push_row); - this.mServerInfoPush = (TextView) findViewById(R.id.server_info_push); - this.mOtrFingerprint = (TextView) findViewById(R.id.otr_fingerprint); - this.mOtrFingerprintBox = (RelativeLayout) findViewById(R.id.otr_fingerprint_box); - this.mOtrFingerprintToClipboardButton = (ImageButton) findViewById(R.id.action_copy_to_clipboard); - this.mAxolotlFingerprint = (TextView) findViewById(R.id.axolotl_fingerprint); - this.mAxolotlFingerprintBox = (RelativeLayout) findViewById(R.id.axolotl_fingerprint_box); - this.mAxolotlFingerprintToClipboardButton = (ImageButton) findViewById(R.id.action_copy_axolotl_to_clipboard); - this.mRegenerateAxolotlKeyButton = (ImageButton) findViewById(R.id.action_regenerate_omemo_key); - this.mOwnFingerprintDesc = (TextView) findViewById(R.id.own_fingerprint_desc); - this.keysCard = (LinearLayout) findViewById(R.id.other_device_keys_card); - this.keys = (LinearLayout) findViewById(R.id.other_device_keys); - this.mNamePort = (LinearLayout) findViewById(R.id.name_port); - this.mHostname = (EditText) findViewById(R.id.hostname); - this.mHostname.addTextChangedListener(mTextWatcher); - this.mPort = (EditText) findViewById(R.id.port); - this.mPort.setText("5222"); - this.mPort.addTextChangedListener(mTextWatcher); - this.mSaveButton = (Button) findViewById(R.id.save_button); - this.mCancelButton = (Button) findViewById(R.id.cancel_button); - this.mSaveButton.setOnClickListener(this.mSaveButtonClickListener); - this.mCancelButton.setOnClickListener(this.mCancelButtonClickListener); - this.mMoreTable = (TableLayout) findViewById(R.id.server_info_more); - if (savedInstanceState != null && savedInstanceState.getBoolean("showMoreTable")) { - changeMoreTableVisibility(true); - } - final OnCheckedChangeListener OnCheckedShowConfirmPassword = new OnCheckedChangeListener() { - @Override - public void onCheckedChanged(final CompoundButton buttonView, - final boolean isChecked) { - if (isChecked) { - mPasswordConfirm.setVisibility(View.VISIBLE); - } else { - mPasswordConfirm.setVisibility(View.GONE); - } - updateSaveButton(); - } - }; - this.mRegisterNew.setOnCheckedChangeListener(OnCheckedShowConfirmPassword); - if (Config.DISALLOW_REGISTRATION_IN_UI) { - this.mRegisterNew.setVisibility(View.GONE); - } - } - - @Override - public boolean onCreateOptionsMenu(final Menu menu) { - super.onCreateOptionsMenu(menu); - getMenuInflater().inflate(R.menu.editaccount, menu); - final MenuItem showQrCode = menu.findItem(R.id.action_show_qr_code); - final MenuItem showBlocklist = menu.findItem(R.id.action_show_block_list); - final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more); - final MenuItem changePassword = menu.findItem(R.id.action_change_password_on_server); - final MenuItem showPassword = menu.findItem(R.id.action_show_password); - final MenuItem clearDevices = menu.findItem(R.id.action_clear_devices); - final MenuItem renewCertificate = menu.findItem(R.id.action_renew_certificate); - final MenuItem mamPrefs = menu.findItem(R.id.action_mam_prefs); - final MenuItem changePresence = menu.findItem(R.id.action_change_presence); - renewCertificate.setVisible(mAccount != null && mAccount.getPrivateKeyAlias() != null); - - if (mAccount != null && mAccount.isOnlineAndConnected()) { - if (!mAccount.getXmppConnection().getFeatures().blocking()) { - showBlocklist.setVisible(false); - } - if (!mAccount.getXmppConnection().getFeatures().register()) { - changePassword.setVisible(false); - } - mamPrefs.setVisible(mAccount.getXmppConnection().getFeatures().mam()); - Set otherDevices = mAccount.getAxolotlService().getOwnDeviceIds(); - if (otherDevices == null || otherDevices.isEmpty() || !Config.supportOmemo()) { - clearDevices.setVisible(false); - } - changePresence.setVisible(manuallyChangePresence()); - } else { - showQrCode.setVisible(false); - showBlocklist.setVisible(false); - showMoreInfo.setVisible(false); - changePassword.setVisible(false); - clearDevices.setVisible(false); - mamPrefs.setVisible(false); - changePresence.setVisible(false); - } - - if (mAccount != null) { - showPassword.setVisible(mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) - && !mAccount.isOptionSet(Account.OPTION_REGISTER)); - } else { - showPassword.setVisible(false); - } - return super.onCreateOptionsMenu(menu); - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more); - if (showMoreInfo.isVisible()) { - showMoreInfo.setChecked(mMoreTable.getVisibility() == View.VISIBLE); - } - return super.onPrepareOptionsMenu(menu); - } - - @Override - protected void onStart() { - super.onStart(); - final int theme = findTheme(); - if (this.mTheme != theme) { - recreate(); - } else if (getIntent() != null) { - try { - this.jidToEdit = Jid.fromString(getIntent().getStringExtra("jid")); - } catch (final InvalidJidException | NullPointerException ignored) { - this.jidToEdit = null; - } - boolean init = getIntent().getBooleanExtra("init", false); - this.mInitMode = init || this.jidToEdit == null; - this.messageFingerprint = getIntent().getStringExtra("fingerprint"); - if (!mInitMode) { - this.mRegisterNew.setVisibility(View.GONE); - if (getActionBar() != null) { - getActionBar().setTitle(getString(R.string.account_details)); - } - } else { - this.mAvatar.setVisibility(View.GONE); - ActionBar ab = getActionBar(); - if (ab != null) { - if (init && Config.MAGIC_CREATE_DOMAIN == null) { - ab.setDisplayShowHomeEnabled(false); - ab.setDisplayHomeAsUpEnabled(false); - } - ab.setTitle(R.string.action_add_account); - } - } - } - SharedPreferences preferences = getPreferences(); - boolean useTor = Config.FORCE_ORBOT || preferences.getBoolean("use_tor", false); - this.mShowOptions = useTor || preferences.getBoolean("show_connection_options", false); - mHostname.setHint(useTor ? R.string.hostname_or_onion : R.string.hostname_example); - this.mNamePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE); - } - - @Override - public void onSaveInstanceState(final Bundle savedInstanceState) { - if (mAccount != null) { - savedInstanceState.putString("account", mAccount.getJid().toBareJid().toString()); - savedInstanceState.putBoolean("initMode", mInitMode); - savedInstanceState.putBoolean("showMoreTable", mMoreTable.getVisibility() == View.VISIBLE); - } - super.onSaveInstanceState(savedInstanceState); - } - - protected void onBackendConnected() { - boolean init = true; - if (mSavedInstanceAccount != null) { - try { - this.mAccount = xmppConnectionService.findAccountByJid(Jid.fromString(mSavedInstanceAccount)); - this.mInitMode = mSavedInstanceInit; - init = false; - } catch (InvalidJidException e) { - this.mAccount = null; - } - - } else if (this.jidToEdit != null) { - this.mAccount = xmppConnectionService.findAccountByJid(jidToEdit); - } - - if (mAccount != null) { - this.mInitMode |= this.mAccount.isOptionSet(Account.OPTION_REGISTER); - this.mUsernameMode |= mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) && mAccount.isOptionSet(Account.OPTION_REGISTER); - if (this.mAccount.getPrivateKeyAlias() != null) { - this.mPassword.setHint(R.string.authenticate_with_certificate); - if (this.mInitMode) { - this.mPassword.requestFocus(); - } - } - updateAccountInformation(init); - } - - - if (Config.MAGIC_CREATE_DOMAIN == null && this.xmppConnectionService.getAccounts().size() == 0) { - this.mCancelButton.setEnabled(false); - this.mCancelButton.setTextColor(getSecondaryTextColor()); - } - if (mUsernameMode) { - this.mAccountJidLabel.setText(R.string.username); - this.mAccountJid.setHint(R.string.username_hint); - } else { - final KnownHostsAdapter mKnownHostsAdapter = new KnownHostsAdapter(this, - R.layout.simple_list_item, - xmppConnectionService.getKnownHosts()); - this.mAccountJid.setAdapter(mKnownHostsAdapter); - } - updateSaveButton(); - invalidateOptionsMenu(); - } - - private String getUserModeDomain() { - if (mAccount != null) { - return mAccount.getJid().getDomainpart(); - } else { - return Config.DOMAIN_LOCK; - } - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case R.id.mgmt_account_reconnect: - if (xmppConnectionServiceBound) { - unbindService(mConnection); - xmppConnectionServiceBound = false; - } - stopService(new Intent(EditAccountActivity.this, - XmppConnectionService.class)); - finish(); - break; - case R.id.action_show_block_list: - final Intent showBlocklistIntent = new Intent(this, BlocklistActivity.class); - showBlocklistIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString()); - startActivity(showBlocklistIntent); - break; - case R.id.action_server_info_show_more: - changeMoreTableVisibility(!item.isChecked()); - break; - case R.id.action_change_password_on_server: - gotoChangePassword(null); - break; - case R.id.action_mam_prefs: - editMamPrefs(); - break; - case R.id.action_clear_devices: - showWipePepDialog(); - break; - case R.id.action_renew_certificate: - renewCertificate(); - break; - case R.id.action_change_presence: - changePresence(); - break; - case R.id.action_show_password: - showPassword(); - break; - } - return super.onOptionsItemSelected(item); - } - - private void changeMoreTableVisibility(boolean visible) { - mMoreTable.setVisibility(visible ? View.VISIBLE : View.GONE); - } - - private void gotoChangePassword(String newPassword) { - final Intent changePasswordIntent = new Intent(this, ChangePasswordActivity.class); - changePasswordIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString()); - if (newPassword != null) { - changePasswordIntent.putExtra("password", newPassword); - } - startActivity(changePasswordIntent); - } - - private void renewCertificate() { - KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null); - } - - private void changePresence() { - Intent intent = new Intent(this, SetPresenceActivity.class); - intent.putExtra(SetPresenceActivity.EXTRA_ACCOUNT,mAccount.getJid().toBareJid().toString()); - startActivity(intent); - } - - @Override - public void alias(String alias) { - if (alias != null) { - xmppConnectionService.updateKeyInAccount(mAccount, alias); - } - } - - private void updateAccountInformation(boolean init) { - if (init) { - this.mAccountJid.getEditableText().clear(); - if (mUsernameMode) { - this.mAccountJid.getEditableText().append(this.mAccount.getJid().getLocalpart()); - } else { - this.mAccountJid.getEditableText().append(this.mAccount.getJid().toBareJid().toString()); - } - this.mPassword.setText(this.mAccount.getPassword()); - this.mHostname.setText(""); - this.mHostname.getEditableText().append(this.mAccount.getHostname()); - this.mPort.setText(""); - this.mPort.getEditableText().append(String.valueOf(this.mAccount.getPort())); - this.mNamePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE); - - } - - if (!mInitMode) { - this.mAvatar.setVisibility(View.VISIBLE); - this.mAvatar.setImageBitmap(avatarService().get(this.mAccount, getPixel(Config.AVATAR_SIZE))); - this.mAccountJid.setEnabled(false); - } else { - this.mAvatar.setVisibility(View.GONE); - } - if (this.mAccount.isOptionSet(Account.OPTION_REGISTER)) { - this.mRegisterNew.setVisibility(View.VISIBLE); - this.mRegisterNew.setChecked(true); - this.mPasswordConfirm.setText(this.mAccount.getPassword()); - } else { - this.mRegisterNew.setVisibility(View.GONE); - this.mRegisterNew.setChecked(false); - } - if (this.mAccount.isOnlineAndConnected() && !this.mFetchingAvatar) { - Features features = this.mAccount.getXmppConnection().getFeatures(); - this.mStats.setVisibility(View.VISIBLE); - boolean showBatteryWarning = !xmppConnectionService.getPushManagementService().available(mAccount) && isOptimizingBattery(); - boolean showDataSaverWarning = isAffectedByDataSaver(); - showOsOptimizationWarning(showBatteryWarning,showDataSaverWarning); - this.mSessionEst.setText(UIHelper.readableTimeDifferenceFull(this, this.mAccount.getXmppConnection() - .getLastSessionEstablished())); - if (features.rosterVersioning()) { - this.mServerInfoRosterVersion.setText(R.string.server_info_available); - } else { - this.mServerInfoRosterVersion.setText(R.string.server_info_unavailable); - } - if (features.carbons()) { - this.mServerInfoCarbons.setText(R.string.server_info_available); - } else { - this.mServerInfoCarbons - .setText(R.string.server_info_unavailable); - } - if (features.mam()) { - this.mServerInfoMam.setText(R.string.server_info_available); - } else { - this.mServerInfoMam.setText(R.string.server_info_unavailable); - } - if (features.csi()) { - this.mServerInfoCSI.setText(R.string.server_info_available); - } else { - this.mServerInfoCSI.setText(R.string.server_info_unavailable); - } - if (features.blocking()) { - this.mServerInfoBlocking.setText(R.string.server_info_available); - } else { - this.mServerInfoBlocking.setText(R.string.server_info_unavailable); - } - if (features.sm()) { - this.mServerInfoSm.setText(R.string.server_info_available); - } else { - this.mServerInfoSm.setText(R.string.server_info_unavailable); - } - if (features.pep()) { - AxolotlService axolotlService = this.mAccount.getAxolotlService(); - if (axolotlService != null && axolotlService.isPepBroken()) { - this.mServerInfoPep.setText(R.string.server_info_broken); - } else { - this.mServerInfoPep.setText(R.string.server_info_available); - } - } else { - this.mServerInfoPep.setText(R.string.server_info_unavailable); - } - if (features.httpUpload(0)) { - this.mServerInfoHttpUpload.setText(R.string.server_info_available); - } else { - this.mServerInfoHttpUpload.setText(R.string.server_info_unavailable); - } - - this.mPushRow.setVisibility(xmppConnectionService.getPushManagementService().isStub() ? View.GONE : View.VISIBLE); - - if (xmppConnectionService.getPushManagementService().available(mAccount)) { - this.mServerInfoPush.setText(R.string.server_info_available); - } else { - this.mServerInfoPush.setText(R.string.server_info_unavailable); - } - final String otrFingerprint = this.mAccount.getOtrFingerprint(); - if (otrFingerprint != null && Config.supportOtr()) { - this.mOtrFingerprintBox.setVisibility(View.VISIBLE); - this.mOtrFingerprint.setText(CryptoHelper.prettifyFingerprint(otrFingerprint)); - this.mOtrFingerprintToClipboardButton - .setVisibility(View.VISIBLE); - this.mOtrFingerprintToClipboardButton - .setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(final View v) { - - if (copyTextToClipboard(otrFingerprint, R.string.otr_fingerprint)) { - Toast.makeText( - EditAccountActivity.this, - R.string.toast_message_otr_fingerprint, - Toast.LENGTH_SHORT).show(); - } - } - }); - } else { - this.mOtrFingerprintBox.setVisibility(View.GONE); - } - final String ownAxolotlFingerprint = this.mAccount.getAxolotlService().getOwnFingerprint(); - if (ownAxolotlFingerprint != null && Config.supportOmemo()) { - this.mAxolotlFingerprintBox.setVisibility(View.VISIBLE); - if (ownAxolotlFingerprint.equals(messageFingerprint)) { - this.mOwnFingerprintDesc.setTextColor(getResources().getColor(R.color.accent)); - } else { - this.mOwnFingerprintDesc.setTextColor(getSecondaryTextColor()); - } - this.mAxolotlFingerprint.setText(CryptoHelper.prettifyFingerprint(ownAxolotlFingerprint.substring(2))); - this.mAxolotlFingerprintToClipboardButton - .setVisibility(View.VISIBLE); - this.mAxolotlFingerprintToClipboardButton - .setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(final View v) { - copyOmemoFingerprint(ownAxolotlFingerprint); - } - }); - if (Config.SHOW_REGENERATE_AXOLOTL_KEYS_BUTTON) { - this.mRegenerateAxolotlKeyButton - .setVisibility(View.VISIBLE); - this.mRegenerateAxolotlKeyButton - .setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(final View v) { - showRegenerateAxolotlKeyDialog(); - } - }); - } - } else { - this.mAxolotlFingerprintBox.setVisibility(View.GONE); - } - boolean hasKeys = false; - keys.removeAllViews(); - for (final String fingerprint : mAccount.getAxolotlService().getFingerprintsForOwnSessions()) { - if (ownAxolotlFingerprint.equals(fingerprint)) { - continue; - } - boolean highlight = fingerprint.equals(messageFingerprint); - hasKeys |= addFingerprintRow(keys, mAccount, fingerprint, highlight); - } - if (hasKeys && Config.supportOmemo()) { - keysCard.setVisibility(View.VISIBLE); - } else { - keysCard.setVisibility(View.GONE); - } - } else { - if (this.mAccount.errorStatus()) { - final EditText errorTextField; - if (this.mAccount.getStatus() == Account.State.UNAUTHORIZED) { - errorTextField = this.mPassword; - } else if (mShowOptions - && this.mAccount.getStatus() == Account.State.SERVER_NOT_FOUND - && this.mHostname.getText().length() > 0) { - errorTextField = this.mHostname; - } else { - errorTextField = this.mAccountJid; - } - errorTextField.setError(getString(this.mAccount.getStatus().getReadableId())); - if (init || !accountInfoEdited()) { - errorTextField.requestFocus(); - } - } else { - this.mAccountJid.setError(null); - this.mPassword.setError(null); - this.mHostname.setError(null); - } - this.mStats.setVisibility(View.GONE); - } - } - - private void showOsOptimizationWarning(boolean showBatteryWarning, boolean showDataSaverWarning) { - this.mOsOptimizations.setVisibility(showBatteryWarning || showDataSaverWarning ? View.VISIBLE : View.GONE); - if (showDataSaverWarning) { - this.mDisableOsOptimizationsHeadline.setText(R.string.data_saver_enabled); - this.getmDisableOsOptimizationsBody.setText(R.string.data_saver_enabled_explained); - this.mDisableOsOptimizationsButton.setText(R.string.allow); - this.mDisableOsOptimizationsButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - Intent intent = new Intent(Settings.ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS); - Uri uri = Uri.parse("package:" + getPackageName()); - intent.setData(uri); - try { - startActivityForResult(intent, REQUEST_DATA_SAVER); - } catch (ActivityNotFoundException e) { - Toast.makeText(EditAccountActivity.this, R.string.device_does_not_support_data_saver, Toast.LENGTH_SHORT).show(); - } - } - }); - } else if (showBatteryWarning) { - this.mDisableOsOptimizationsButton.setText(R.string.disable); - this.mDisableOsOptimizationsHeadline.setText(R.string.battery_optimizations_enabled); - this.getmDisableOsOptimizationsBody.setText(R.string.battery_optimizations_enabled_explained); - this.mDisableOsOptimizationsButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); - Uri uri = Uri.parse("package:" + getPackageName()); - intent.setData(uri); - try { - startActivityForResult(intent, REQUEST_BATTERY_OP); - } catch (ActivityNotFoundException e) { - Toast.makeText(EditAccountActivity.this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show(); - } - } - }); - } - } - - - public void showRegenerateAxolotlKeyDialog() { - Builder builder = new Builder(this); - builder.setTitle("Regenerate Key"); - builder.setIconAttribute(android.R.attr.alertDialogIcon); - builder.setMessage("Are you sure you want to regenerate your Identity Key? (This will also wipe all established sessions and contact Identity Keys)"); - builder.setNegativeButton(getString(R.string.cancel), null); - builder.setPositiveButton("Yes", - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - mAccount.getAxolotlService().regenerateKeys(false); - } - }); - builder.create().show(); - } - - public void showWipePepDialog() { - Builder builder = new Builder(this); - builder.setTitle(getString(R.string.clear_other_devices)); - builder.setIconAttribute(android.R.attr.alertDialogIcon); - builder.setMessage(getString(R.string.clear_other_devices_desc)); - builder.setNegativeButton(getString(R.string.cancel), null); - builder.setPositiveButton(getString(R.string.accept), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - mAccount.getAxolotlService().wipeOtherPepDevices(); - } - }); - builder.create().show(); - } - - private void editMamPrefs() { - this.mFetchingMamPrefsToast = Toast.makeText(this, R.string.fetching_mam_prefs, Toast.LENGTH_LONG); - this.mFetchingMamPrefsToast.show(); - xmppConnectionService.fetchMamPreferences(mAccount, this); - } - - private void showPassword() { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - View view = getLayoutInflater().inflate(R.layout.dialog_show_password, null); - TextView password = (TextView) view.findViewById(R.id.password); - password.setText(mAccount.getPassword()); - builder.setTitle(R.string.password); - builder.setView(view); - builder.setPositiveButton(R.string.cancel, null); - builder.create().show(); - } - - @Override - public void onKeyStatusUpdated(AxolotlService.FetchStatus report) { - refreshUi(); - } - - @Override - public void onCaptchaRequested(final Account account, final String id, final Data data, final Bitmap captcha) { - runOnUiThread(new Runnable() { - @Override - public void run() { - if ((mCaptchaDialog != null) && mCaptchaDialog.isShowing()) { - mCaptchaDialog.dismiss(); - } - final AlertDialog.Builder builder = new AlertDialog.Builder(EditAccountActivity.this); - final View view = getLayoutInflater().inflate(R.layout.captcha, null); - final ImageView imageView = (ImageView) view.findViewById(R.id.captcha); - final EditText input = (EditText) view.findViewById(R.id.input); - imageView.setImageBitmap(captcha); - - builder.setTitle(getString(R.string.captcha_required)); - builder.setView(view); - - builder.setPositiveButton(getString(R.string.ok), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - String rc = input.getText().toString(); - data.put("username", account.getUsername()); - data.put("password", account.getPassword()); - data.put("ocr", rc); - data.submit(); - - if (xmppConnectionServiceBound) { - xmppConnectionService.sendCreateAccountWithCaptchaPacket( - account, id, data); - } - } - }); - builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (xmppConnectionService != null) { - xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, null, null); - } - } - }); - - builder.setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - if (xmppConnectionService != null) { - xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, null, null); - } - } - }); - mCaptchaDialog = builder.create(); - mCaptchaDialog.show(); - } - }); - } - - public void onShowErrorToast(final int resId) { - runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(EditAccountActivity.this, resId, Toast.LENGTH_SHORT).show(); - } - }); - } - - @Override - public void onPreferencesFetched(final Element prefs) { - runOnUiThread(new Runnable() { - @Override - public void run() { - if (mFetchingMamPrefsToast != null) { - mFetchingMamPrefsToast.cancel(); - } - AlertDialog.Builder builder = new Builder(EditAccountActivity.this); - builder.setTitle(R.string.server_side_mam_prefs); - String defaultAttr = prefs.getAttribute("default"); - final List defaults = Arrays.asList("never", "roster", "always"); - final AtomicInteger choice = new AtomicInteger(Math.max(0,defaults.indexOf(defaultAttr))); - builder.setSingleChoiceItems(R.array.mam_prefs, choice.get(), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - choice.set(which); - } - }); - builder.setNegativeButton(R.string.cancel, null); - builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - prefs.setAttribute("default",defaults.get(choice.get())); - xmppConnectionService.pushMamPreferences(mAccount, prefs); - } - }); - builder.create().show(); - } - }); - } - - @Override - public void onPreferencesFetchFailed() { - runOnUiThread(new Runnable() { - @Override - public void run() { - if (mFetchingMamPrefsToast != null) { - mFetchingMamPrefsToast.cancel(); - } - Toast.makeText(EditAccountActivity.this,R.string.unable_to_fetch_mam_prefs,Toast.LENGTH_LONG).show(); - } - }); - } + OnKeyStatusUpdated, OnCaptchaRequested, KeyChainAliasCallback, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnMamPreferencesFetched { + + private static final int REQUEST_DATA_SAVER = 0x37af244; + private AutoCompleteTextView mAccountJid; + private EditText mPassword; + private EditText mPasswordConfirm; + private CheckBox mRegisterNew; + private Button mCancelButton; + private Button mSaveButton; + private Button mDisableOsOptimizationsButton; + private TextView mDisableOsOptimizationsHeadline; + private TextView getmDisableOsOptimizationsBody; + private TableLayout mMoreTable; + + private LinearLayout mStats; + private RelativeLayout mOsOptimizations; + private TextView mServerInfoSm; + private TextView mServerInfoRosterVersion; + private TextView mServerInfoCarbons; + private TextView mServerInfoMam; + private TextView mServerInfoCSI; + private TextView mServerInfoBlocking; + private TextView mServerInfoPep; + private TextView mServerInfoHttpUpload; + private TextView mServerInfoPush; + private TextView mSessionEst; + private TextView mOtrFingerprint; + private TextView mAxolotlFingerprint; + private TextView mOwnFingerprintDesc; + private TextView mAccountJidLabel; + private ImageView mAvatar; + private RelativeLayout mOtrFingerprintBox; + private RelativeLayout mAxolotlFingerprintBox; + private ImageButton mOtrFingerprintToClipboardButton; + private ImageButton mAxolotlFingerprintToClipboardButton; + private ImageButton mRegenerateAxolotlKeyButton; + private LinearLayout keys; + private LinearLayout keysCard; + private LinearLayout mNamePort; + private EditText mHostname; + private EditText mPort; + private AlertDialog mCaptchaDialog = null; + + private Jid jidToEdit; + private boolean mInitMode = false; + private boolean mUsernameMode = Config.DOMAIN_LOCK != null; + private boolean mShowOptions = false; + private Account mAccount; + private String messageFingerprint; + + private boolean mFetchingAvatar = false; + + private final OnClickListener mSaveButtonClickListener = new OnClickListener() { + + @Override + public void onClick(final View v) { + final String password = mPassword.getText().toString(); + final String passwordConfirm = mPasswordConfirm.getText().toString(); + + if (!mInitMode && passwordChangedInMagicCreateMode()) { + gotoChangePassword(password); + return; + } + if (mInitMode && mAccount != null) { + mAccount.setOption(Account.OPTION_DISABLED, false); + } + if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED && !accountInfoEdited()) { + mAccount.setOption(Account.OPTION_DISABLED, false); + if (!xmppConnectionService.updateAccount(mAccount)) { + Toast.makeText(EditAccountActivity.this, R.string.unable_to_update_account, Toast.LENGTH_SHORT).show(); + } + return; + } + final boolean registerNewAccount = mRegisterNew.isChecked() && !Config.DISALLOW_REGISTRATION_IN_UI; + if (mUsernameMode && mAccountJid.getText().toString().contains("@")) { + mAccountJid.setError(getString(R.string.invalid_username)); + mAccountJid.requestFocus(); + return; + } + final Jid jid; + try { + if (mUsernameMode) { + jid = Jid.fromParts(mAccountJid.getText().toString(), getUserModeDomain(), null); + } else { + jid = Jid.fromString(mAccountJid.getText().toString()); + } + } catch (final InvalidJidException e) { + if (mUsernameMode) { + mAccountJid.setError(getString(R.string.invalid_username)); + } else { + mAccountJid.setError(getString(R.string.invalid_jid)); + } + mAccountJid.requestFocus(); + return; + } + String hostname = null; + int numericPort = 5222; + if (mShowOptions) { + hostname = mHostname.getText().toString().replaceAll("\\s", ""); + final String port = mPort.getText().toString().replaceAll("\\s", ""); + if (hostname.contains(" ")) { + mHostname.setError(getString(R.string.not_valid_hostname)); + mHostname.requestFocus(); + return; + } + try { + numericPort = Integer.parseInt(port); + if (numericPort < 0 || numericPort > 65535) { + mPort.setError(getString(R.string.not_a_valid_port)); + mPort.requestFocus(); + return; + } + + } catch (NumberFormatException e) { + mPort.setError(getString(R.string.not_a_valid_port)); + mPort.requestFocus(); + return; + } + } + + if (jid.isDomainJid()) { + if (mUsernameMode) { + mAccountJid.setError(getString(R.string.invalid_username)); + } else { + mAccountJid.setError(getString(R.string.invalid_jid)); + } + mAccountJid.requestFocus(); + return; + } + if (registerNewAccount) { + if (XmppConnection.errorMessage != null) { + Toast.makeText(EditAccountActivity.this, XmppConnection.errorMessage, Toast.LENGTH_LONG).show(); + } + if (!password.equals(passwordConfirm)) { + mPasswordConfirm.setError(getString(R.string.passwords_do_not_match)); + mPasswordConfirm.requestFocus(); + return; + } + } + if (mAccount != null) { + if (mInitMode && mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)) { + mAccount.setOption(Account.OPTION_MAGIC_CREATE, mAccount.getPassword().contains(password)); + } + mAccount.setJid(jid); + mAccount.setPort(numericPort); + mAccount.setHostname(hostname); + if (XmppConnection.errorMessage != null) { + mAccountJid.setError(XmppConnection.errorMessage); + } else { + mAccountJid.setError(null); + } + mPasswordConfirm.setError(null); + mAccount.setPassword(password); + mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount); + if (!xmppConnectionService.updateAccount(mAccount)) { + Toast.makeText(EditAccountActivity.this, R.string.unable_to_update_account, Toast.LENGTH_SHORT).show(); + return; + } + } else { + if (xmppConnectionService.findAccountByJid(jid) != null) { + mAccountJid.setError(getString(R.string.account_already_exists)); + mAccountJid.requestFocus(); + return; + } + mAccount = new Account(jid.toBareJid(), password); + mAccount.setPort(numericPort); + mAccount.setHostname(hostname); + mAccount.setOption(Account.OPTION_USETLS, true); + mAccount.setOption(Account.OPTION_USECOMPRESSION, true); + mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount); + xmppConnectionService.createAccount(mAccount); + } + mHostname.setError(null); + mPort.setError(null); + if (!mAccount.isOptionSet(Account.OPTION_DISABLED) + && !registerNewAccount + && !mInitMode) { + finish(); + } else { + updateSaveButton(); + updateAccountInformation(true); + } + + } + }; + private final OnClickListener mCancelButtonClickListener = new OnClickListener() { + + @Override + public void onClick(final View v) { + deleteMagicCreatedAccountAndReturnIfNecessary(); + finish(); + } + }; + private Toast mFetchingMamPrefsToast; + private TableRow mPushRow; + private String mSavedInstanceAccount; + private boolean mSavedInstanceInit = false; + + public void refreshUiReal() { + invalidateOptionsMenu(); + if (mAccount != null + && mAccount.getStatus() != Account.State.ONLINE + && mFetchingAvatar) { + startActivity(new Intent(getApplicationContext(), + ManageAccountActivity.class)); + finish(); + } else if (mInitMode && mAccount != null && mAccount.getStatus() == Account.State.ONLINE) { + if (!mFetchingAvatar) { + mFetchingAvatar = true; + xmppConnectionService.checkForAvatar(mAccount, mAvatarFetchCallback); + } + } + if (mAccount != null) { + updateAccountInformation(false); + } + updateSaveButton(); + } + + @Override + public boolean onNavigateUp() { + deleteMagicCreatedAccountAndReturnIfNecessary(); + return super.onNavigateUp(); + } + + @Override + public void onBackPressed() { + deleteMagicCreatedAccountAndReturnIfNecessary(); + super.onBackPressed(); + } + + private void deleteMagicCreatedAccountAndReturnIfNecessary() { + if (Config.MAGIC_CREATE_DOMAIN != null + && mAccount != null + && mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) + && mAccount.isOptionSet(Account.OPTION_REGISTER) + && xmppConnectionService.getAccounts().size() == 1) { + xmppConnectionService.deleteAccount(mAccount); + startActivity(new Intent(EditAccountActivity.this, WelcomeActivity.class)); + } + } + + @Override + public void onAccountUpdate() { + refreshUi(); + } + + private final UiCallback mAvatarFetchCallback = new UiCallback() { + + @Override + public void userInputRequried(final PendingIntent pi, final Avatar avatar) { + finishInitialSetup(avatar); + } + + @Override + public void success(final Avatar avatar) { + finishInitialSetup(avatar); + } + + @Override + public void error(final int errorCode, final Avatar avatar) { + finishInitialSetup(avatar); + } + }; + private final TextWatcher mTextWatcher = new TextWatcher() { + + @Override + public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { + updateSaveButton(); + } + + @Override + public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { + } + + @Override + public void afterTextChanged(final Editable s) { + + } + }; + + private final OnClickListener mAvatarClickListener = new OnClickListener() { + @Override + public void onClick(final View view) { + if (mAccount != null) { + final Intent intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class); + intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toBareJid().toString()); + startActivity(intent); + } + } + }; + + protected void finishInitialSetup(final Avatar avatar) { + runOnUiThread(new Runnable() { + + @Override + public void run() { + final Intent intent; + final XmppConnection connection = mAccount.getXmppConnection(); + final boolean wasFirstAccount = xmppConnectionService != null && xmppConnectionService.getAccounts().size() == 1; + if (avatar != null || (connection != null && !connection.getFeatures().pep())) { + intent = new Intent(getApplicationContext(), StartConversationActivity.class); + if (wasFirstAccount) { + intent.putExtra("init", true); + } + } else { + intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class); + intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toBareJid().toString()); + intent.putExtra("setup", true); + } + if (wasFirstAccount) { + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + } + startActivity(intent); + finish(); + } + }); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_BATTERY_OP || requestCode == REQUEST_DATA_SAVER) { + updateAccountInformation(mAccount == null); + } + } + + protected void updateSaveButton() { + boolean accountInfoEdited = accountInfoEdited(); + + if (!mInitMode && passwordChangedInMagicCreateMode()) { + this.mSaveButton.setText(R.string.change_password); + this.mSaveButton.setEnabled(true); + this.mSaveButton.setTextColor(getPrimaryTextColor()); + } else if (accountInfoEdited && !mInitMode) { + this.mSaveButton.setText(R.string.save); + this.mSaveButton.setEnabled(true); + this.mSaveButton.setTextColor(getPrimaryTextColor()); + } else if (mAccount != null + && (mAccount.getStatus() == Account.State.CONNECTING || mAccount.getStatus() == Account.State.REGISTRATION_SUCCESSFUL || mFetchingAvatar)) { + this.mSaveButton.setEnabled(false); + this.mSaveButton.setTextColor(getSecondaryTextColor()); + this.mSaveButton.setText(R.string.account_status_connecting); + } else if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED && !mInitMode) { + this.mSaveButton.setEnabled(true); + this.mSaveButton.setTextColor(getPrimaryTextColor()); + this.mSaveButton.setText(R.string.enable); + } else { + this.mSaveButton.setEnabled(true); + this.mSaveButton.setTextColor(getPrimaryTextColor()); + if (!mInitMode) { + if (mAccount != null && mAccount.isOnlineAndConnected()) { + this.mSaveButton.setText(R.string.save); + if (!accountInfoEdited) { + this.mSaveButton.setEnabled(false); + this.mSaveButton.setTextColor(getSecondaryTextColor()); + } + } else { + this.mSaveButton.setText(R.string.connect); + } + } else { + this.mSaveButton.setText(R.string.next); + } + } + } + + protected boolean accountInfoEdited() { + if (this.mAccount == null) { + return false; + } + return jidEdited() || + !this.mAccount.getPassword().equals(this.mPassword.getText().toString()) || + !this.mAccount.getHostname().equals(this.mHostname.getText().toString()) || + !String.valueOf(this.mAccount.getPort()).equals(this.mPort.getText().toString()); + } + + protected boolean jidEdited() { + final String unmodified; + if (mUsernameMode) { + unmodified = this.mAccount.getJid().getLocalpart(); + } else { + unmodified = this.mAccount.getJid().toBareJid().toString(); + } + return !unmodified.equals(this.mAccountJid.getText().toString()); + } + + protected boolean passwordChangedInMagicCreateMode() { + return mAccount != null + && mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) + && !this.mAccount.getPassword().equals(this.mPassword.getText().toString()) + && !this.jidEdited() + && mAccount.isOnlineAndConnected(); + } + + @Override + protected String getShareableUri() { + if (mAccount != null) { + return mAccount.getShareableUri(); + } else { + return ""; + } + } + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (savedInstanceState != null) { + this.mSavedInstanceAccount = savedInstanceState.getString("account"); + this.mSavedInstanceInit = savedInstanceState.getBoolean("initMode", false); + } + setContentView(R.layout.activity_edit_account); + this.mAccountJid = (AutoCompleteTextView) findViewById(R.id.account_jid); + this.mAccountJid.addTextChangedListener(this.mTextWatcher); + this.mAccountJidLabel = (TextView) findViewById(R.id.account_jid_label); + this.mPassword = (EditText) findViewById(R.id.account_password); + this.mPassword.addTextChangedListener(this.mTextWatcher); + this.mPasswordConfirm = (EditText) findViewById(R.id.account_password_confirm); + this.mAvatar = (ImageView) findViewById(R.id.avater); + this.mAvatar.setOnClickListener(this.mAvatarClickListener); + this.mRegisterNew = (CheckBox) findViewById(R.id.account_register_new); + this.mStats = (LinearLayout) findViewById(R.id.stats); + this.mOsOptimizations = (RelativeLayout) findViewById(R.id.os_optimization); + this.mDisableOsOptimizationsButton = (Button) findViewById(R.id.os_optimization_disable); + this.mDisableOsOptimizationsHeadline = (TextView) findViewById(R.id.os_optimization_headline); + this.getmDisableOsOptimizationsBody = (TextView) findViewById(R.id.os_optimization_body); + this.mSessionEst = (TextView) findViewById(R.id.session_est); + this.mServerInfoRosterVersion = (TextView) findViewById(R.id.server_info_roster_version); + this.mServerInfoCarbons = (TextView) findViewById(R.id.server_info_carbons); + this.mServerInfoMam = (TextView) findViewById(R.id.server_info_mam); + this.mServerInfoCSI = (TextView) findViewById(R.id.server_info_csi); + this.mServerInfoBlocking = (TextView) findViewById(R.id.server_info_blocking); + this.mServerInfoSm = (TextView) findViewById(R.id.server_info_sm); + this.mServerInfoPep = (TextView) findViewById(R.id.server_info_pep); + this.mServerInfoHttpUpload = (TextView) findViewById(R.id.server_info_http_upload); + this.mPushRow = (TableRow) findViewById(R.id.push_row); + this.mServerInfoPush = (TextView) findViewById(R.id.server_info_push); + this.mOtrFingerprint = (TextView) findViewById(R.id.otr_fingerprint); + this.mOtrFingerprintBox = (RelativeLayout) findViewById(R.id.otr_fingerprint_box); + this.mOtrFingerprintToClipboardButton = (ImageButton) findViewById(R.id.action_copy_to_clipboard); + this.mAxolotlFingerprint = (TextView) findViewById(R.id.axolotl_fingerprint); + this.mAxolotlFingerprintBox = (RelativeLayout) findViewById(R.id.axolotl_fingerprint_box); + this.mAxolotlFingerprintToClipboardButton = (ImageButton) findViewById(R.id.action_copy_axolotl_to_clipboard); + this.mRegenerateAxolotlKeyButton = (ImageButton) findViewById(R.id.action_regenerate_omemo_key); + this.mOwnFingerprintDesc = (TextView) findViewById(R.id.own_fingerprint_desc); + this.keysCard = (LinearLayout) findViewById(R.id.other_device_keys_card); + this.keys = (LinearLayout) findViewById(R.id.other_device_keys); + this.mNamePort = (LinearLayout) findViewById(R.id.name_port); + this.mHostname = (EditText) findViewById(R.id.hostname); + this.mHostname.addTextChangedListener(mTextWatcher); + this.mPort = (EditText) findViewById(R.id.port); + this.mPort.setText("5222"); + this.mPort.addTextChangedListener(mTextWatcher); + this.mSaveButton = (Button) findViewById(R.id.save_button); + this.mCancelButton = (Button) findViewById(R.id.cancel_button); + this.mSaveButton.setOnClickListener(this.mSaveButtonClickListener); + this.mCancelButton.setOnClickListener(this.mCancelButtonClickListener); + this.mMoreTable = (TableLayout) findViewById(R.id.server_info_more); + if (savedInstanceState != null && savedInstanceState.getBoolean("showMoreTable")) { + changeMoreTableVisibility(true); + } + final OnCheckedChangeListener OnCheckedShowConfirmPassword = new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(final CompoundButton buttonView, + final boolean isChecked) { + if (isChecked) { + mPasswordConfirm.setVisibility(View.VISIBLE); + } else { + mPasswordConfirm.setVisibility(View.GONE); + } + updateSaveButton(); + } + }; + this.mRegisterNew.setOnCheckedChangeListener(OnCheckedShowConfirmPassword); + if (Config.DISALLOW_REGISTRATION_IN_UI) { + this.mRegisterNew.setVisibility(View.GONE); + } + } + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + super.onCreateOptionsMenu(menu); + getMenuInflater().inflate(R.menu.editaccount, menu); + final MenuItem showQrCode = menu.findItem(R.id.action_show_qr_code); + final MenuItem showBlocklist = menu.findItem(R.id.action_show_block_list); + final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more); + final MenuItem changePassword = menu.findItem(R.id.action_change_password_on_server); + final MenuItem showPassword = menu.findItem(R.id.action_show_password); + final MenuItem clearDevices = menu.findItem(R.id.action_clear_devices); + final MenuItem renewCertificate = menu.findItem(R.id.action_renew_certificate); + final MenuItem mamPrefs = menu.findItem(R.id.action_mam_prefs); + final MenuItem changePresence = menu.findItem(R.id.action_change_presence); + renewCertificate.setVisible(mAccount != null && mAccount.getPrivateKeyAlias() != null); + + if (mAccount != null && mAccount.isOnlineAndConnected()) { + if (!mAccount.getXmppConnection().getFeatures().blocking()) { + showBlocklist.setVisible(false); + } + if (!mAccount.getXmppConnection().getFeatures().register()) { + changePassword.setVisible(false); + } + mamPrefs.setVisible(mAccount.getXmppConnection().getFeatures().mam()); + Set otherDevices = mAccount.getAxolotlService().getOwnDeviceIds(); + if (otherDevices == null || otherDevices.isEmpty() || !Config.supportOmemo()) { + clearDevices.setVisible(false); + } + changePresence.setVisible(manuallyChangePresence()); + } else { + showQrCode.setVisible(false); + showBlocklist.setVisible(false); + showMoreInfo.setVisible(false); + changePassword.setVisible(false); + clearDevices.setVisible(false); + mamPrefs.setVisible(false); + changePresence.setVisible(false); + } + + if (mAccount != null) { + showPassword.setVisible(mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) + && !mAccount.isOptionSet(Account.OPTION_REGISTER)); + } else { + showPassword.setVisible(false); + } + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more); + if (showMoreInfo.isVisible()) { + showMoreInfo.setChecked(mMoreTable.getVisibility() == View.VISIBLE); + } + return super.onPrepareOptionsMenu(menu); + } + + @Override + protected void onStart() { + super.onStart(); + final int theme = findTheme(); + if (this.mTheme != theme) { + recreate(); + } else if (getIntent() != null) { + try { + this.jidToEdit = Jid.fromString(getIntent().getStringExtra("jid")); + } catch (final InvalidJidException | NullPointerException ignored) { + this.jidToEdit = null; + } + boolean init = getIntent().getBooleanExtra("init", false); + this.mInitMode = init || this.jidToEdit == null; + this.messageFingerprint = getIntent().getStringExtra("fingerprint"); + if (!mInitMode) { + this.mRegisterNew.setVisibility(View.GONE); + if (getActionBar() != null) { + getActionBar().setTitle(getString(R.string.account_details)); + } + } else { + this.mAvatar.setVisibility(View.GONE); + ActionBar ab = getActionBar(); + if (ab != null) { + if (init && Config.MAGIC_CREATE_DOMAIN == null) { + ab.setDisplayShowHomeEnabled(false); + ab.setDisplayHomeAsUpEnabled(false); + } + ab.setTitle(R.string.action_add_account); + } + } + } + SharedPreferences preferences = getPreferences(); + boolean useTor = Config.FORCE_ORBOT || preferences.getBoolean("use_tor", false); + this.mShowOptions = useTor || preferences.getBoolean("show_connection_options", false); + mHostname.setHint(useTor ? R.string.hostname_or_onion : R.string.hostname_example); + this.mNamePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE); + } + + @Override + public void onSaveInstanceState(final Bundle savedInstanceState) { + if (mAccount != null) { + savedInstanceState.putString("account", mAccount.getJid().toBareJid().toString()); + savedInstanceState.putBoolean("initMode", mInitMode); + savedInstanceState.putBoolean("showMoreTable", mMoreTable.getVisibility() == View.VISIBLE); + } + super.onSaveInstanceState(savedInstanceState); + } + + protected void onBackendConnected() { + boolean init = true; + if (mSavedInstanceAccount != null) { + try { + this.mAccount = xmppConnectionService.findAccountByJid(Jid.fromString(mSavedInstanceAccount)); + this.mInitMode = mSavedInstanceInit; + init = false; + } catch (InvalidJidException e) { + this.mAccount = null; + } + + } else if (this.jidToEdit != null) { + this.mAccount = xmppConnectionService.findAccountByJid(jidToEdit); + } + + if (mAccount != null) { + this.mInitMode |= this.mAccount.isOptionSet(Account.OPTION_REGISTER); + this.mUsernameMode |= mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) && mAccount.isOptionSet(Account.OPTION_REGISTER); + if (this.mAccount.getPrivateKeyAlias() != null) { + this.mPassword.setHint(R.string.authenticate_with_certificate); + if (this.mInitMode) { + this.mPassword.requestFocus(); + } + } + updateAccountInformation(init); + } + + + if (Config.MAGIC_CREATE_DOMAIN == null && this.xmppConnectionService.getAccounts().size() == 0) { + this.mCancelButton.setEnabled(false); + this.mCancelButton.setTextColor(getSecondaryTextColor()); + } + if (mUsernameMode) { + this.mAccountJidLabel.setText(R.string.username); + this.mAccountJid.setHint(R.string.username_hint); + } else { + final KnownHostsAdapter mKnownHostsAdapter = new KnownHostsAdapter(this, + R.layout.simple_list_item, + xmppConnectionService.getKnownHosts()); + this.mAccountJid.setAdapter(mKnownHostsAdapter); + } + updateSaveButton(); + invalidateOptionsMenu(); + } + + private String getUserModeDomain() { + if (mAccount != null) { + return mAccount.getJid().getDomainpart(); + } else { + return Config.DOMAIN_LOCK; + } + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + switch (item.getItemId()) { + case R.id.mgmt_account_reconnect: + if (xmppConnectionServiceBound) { + unbindService(mConnection); + xmppConnectionServiceBound = false; + } + stopService(new Intent(EditAccountActivity.this, + XmppConnectionService.class)); + finish(); + break; + case R.id.action_show_block_list: + final Intent showBlocklistIntent = new Intent(this, BlocklistActivity.class); + showBlocklistIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString()); + startActivity(showBlocklistIntent); + break; + case R.id.action_server_info_show_more: + changeMoreTableVisibility(!item.isChecked()); + break; + case R.id.action_change_password_on_server: + gotoChangePassword(null); + break; + case R.id.action_mam_prefs: + editMamPrefs(); + break; + case R.id.action_clear_devices: + showWipePepDialog(); + break; + case R.id.action_renew_certificate: + renewCertificate(); + break; + case R.id.action_change_presence: + changePresence(); + break; + case R.id.action_show_password: + showPassword(); + break; + } + return super.onOptionsItemSelected(item); + } + + private void changeMoreTableVisibility(boolean visible) { + mMoreTable.setVisibility(visible ? View.VISIBLE : View.GONE); + } + + private void gotoChangePassword(String newPassword) { + final Intent changePasswordIntent = new Intent(this, ChangePasswordActivity.class); + changePasswordIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString()); + if (newPassword != null) { + changePasswordIntent.putExtra("password", newPassword); + } + startActivity(changePasswordIntent); + } + + private void renewCertificate() { + KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null); + } + + private void changePresence() { + Intent intent = new Intent(this, SetPresenceActivity.class); + intent.putExtra(SetPresenceActivity.EXTRA_ACCOUNT, mAccount.getJid().toBareJid().toString()); + startActivity(intent); + } + + @Override + public void alias(String alias) { + if (alias != null) { + xmppConnectionService.updateKeyInAccount(mAccount, alias); + } + } + + private void updateAccountInformation(boolean init) { + if (init) { + this.mAccountJid.getEditableText().clear(); + if (mUsernameMode) { + this.mAccountJid.getEditableText().append(this.mAccount.getJid().getLocalpart()); + } else { + this.mAccountJid.getEditableText().append(this.mAccount.getJid().toBareJid().toString()); + } + this.mPassword.setText(this.mAccount.getPassword()); + this.mHostname.setText(""); + this.mHostname.getEditableText().append(this.mAccount.getHostname()); + this.mPort.setText(""); + this.mPort.getEditableText().append(String.valueOf(this.mAccount.getPort())); + this.mNamePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE); + + } + + if (!mInitMode) { + this.mAvatar.setVisibility(View.VISIBLE); + this.mAvatar.setImageBitmap(avatarService().get(this.mAccount, getPixel(Config.AVATAR_SIZE))); + this.mAccountJid.setEnabled(false); + } else { + this.mAvatar.setVisibility(View.GONE); + } + if (this.mAccount.isOptionSet(Account.OPTION_REGISTER)) { + this.mRegisterNew.setVisibility(View.VISIBLE); + this.mRegisterNew.setChecked(true); + this.mPasswordConfirm.setText(this.mAccount.getPassword()); + } else { + this.mRegisterNew.setVisibility(View.GONE); + this.mRegisterNew.setChecked(false); + } + if (this.mAccount.isOnlineAndConnected() && !this.mFetchingAvatar) { + Features features = this.mAccount.getXmppConnection().getFeatures(); + this.mStats.setVisibility(View.VISIBLE); + boolean showBatteryWarning = !xmppConnectionService.getPushManagementService().available(mAccount) && isOptimizingBattery(); + boolean showDataSaverWarning = isAffectedByDataSaver(); + showOsOptimizationWarning(showBatteryWarning, showDataSaverWarning); + this.mSessionEst.setText(UIHelper.readableTimeDifferenceFull(this, this.mAccount.getXmppConnection() + .getLastSessionEstablished())); + if (features.rosterVersioning()) { + this.mServerInfoRosterVersion.setText(R.string.server_info_available); + } else { + this.mServerInfoRosterVersion.setText(R.string.server_info_unavailable); + } + if (features.carbons()) { + this.mServerInfoCarbons.setText(R.string.server_info_available); + } else { + this.mServerInfoCarbons + .setText(R.string.server_info_unavailable); + } + if (features.mam()) { + this.mServerInfoMam.setText(R.string.server_info_available); + } else { + this.mServerInfoMam.setText(R.string.server_info_unavailable); + } + if (features.csi()) { + this.mServerInfoCSI.setText(R.string.server_info_available); + } else { + this.mServerInfoCSI.setText(R.string.server_info_unavailable); + } + if (features.blocking()) { + this.mServerInfoBlocking.setText(R.string.server_info_available); + } else { + this.mServerInfoBlocking.setText(R.string.server_info_unavailable); + } + if (features.sm()) { + this.mServerInfoSm.setText(R.string.server_info_available); + } else { + this.mServerInfoSm.setText(R.string.server_info_unavailable); + } + if (features.pep()) { + AxolotlService axolotlService = this.mAccount.getAxolotlService(); + if (axolotlService != null && axolotlService.isPepBroken()) { + this.mServerInfoPep.setText(R.string.server_info_broken); + } else { + this.mServerInfoPep.setText(R.string.server_info_available); + } + } else { + this.mServerInfoPep.setText(R.string.server_info_unavailable); + } + if (features.httpUpload(0)) { + this.mServerInfoHttpUpload.setText(R.string.server_info_available); + } else { + this.mServerInfoHttpUpload.setText(R.string.server_info_unavailable); + } + + this.mPushRow.setVisibility(xmppConnectionService.getPushManagementService().isStub() ? View.GONE : View.VISIBLE); + + if (xmppConnectionService.getPushManagementService().available(mAccount)) { + this.mServerInfoPush.setText(R.string.server_info_available); + } else { + this.mServerInfoPush.setText(R.string.server_info_unavailable); + } + final String otrFingerprint = this.mAccount.getOtrFingerprint(); + if (otrFingerprint != null && Config.supportOtr()) { + this.mOtrFingerprintBox.setVisibility(View.VISIBLE); + this.mOtrFingerprint.setText(CryptoHelper.prettifyFingerprint(otrFingerprint)); + this.mOtrFingerprintToClipboardButton + .setVisibility(View.VISIBLE); + this.mOtrFingerprintToClipboardButton + .setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(final View v) { + + if (copyTextToClipboard(otrFingerprint, R.string.otr_fingerprint)) { + Toast.makeText( + EditAccountActivity.this, + R.string.toast_message_otr_fingerprint, + Toast.LENGTH_SHORT).show(); + } + } + }); + } else { + this.mOtrFingerprintBox.setVisibility(View.GONE); + } + final String ownAxolotlFingerprint = this.mAccount.getAxolotlService().getOwnFingerprint(); + if (ownAxolotlFingerprint != null && Config.supportOmemo()) { + this.mAxolotlFingerprintBox.setVisibility(View.VISIBLE); + if (ownAxolotlFingerprint.equals(messageFingerprint)) { + this.mOwnFingerprintDesc.setTextColor(getResources().getColor(R.color.accent)); + } else { + this.mOwnFingerprintDesc.setTextColor(getSecondaryTextColor()); + } + this.mAxolotlFingerprint.setText(CryptoHelper.prettifyFingerprint(ownAxolotlFingerprint.substring(2))); + this.mAxolotlFingerprintToClipboardButton + .setVisibility(View.VISIBLE); + this.mAxolotlFingerprintToClipboardButton + .setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(final View v) { + copyOmemoFingerprint(ownAxolotlFingerprint); + } + }); + if (Config.SHOW_REGENERATE_AXOLOTL_KEYS_BUTTON) { + this.mRegenerateAxolotlKeyButton + .setVisibility(View.VISIBLE); + this.mRegenerateAxolotlKeyButton + .setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(final View v) { + showRegenerateAxolotlKeyDialog(); + } + }); + } + } else { + this.mAxolotlFingerprintBox.setVisibility(View.GONE); + } + boolean hasKeys = false; + keys.removeAllViews(); + for (final String fingerprint : mAccount.getAxolotlService().getFingerprintsForOwnSessions()) { + if (ownAxolotlFingerprint.equals(fingerprint)) { + continue; + } + boolean highlight = fingerprint.equals(messageFingerprint); + hasKeys |= addFingerprintRow(keys, mAccount, fingerprint, highlight); + } + if (hasKeys && Config.supportOmemo()) { + keysCard.setVisibility(View.VISIBLE); + } else { + keysCard.setVisibility(View.GONE); + } + } else { + if (this.mAccount.errorStatus()) { + final EditText errorTextField; + if (this.mAccount.getStatus() == Account.State.UNAUTHORIZED) { + errorTextField = this.mPassword; + } else if (mShowOptions + && this.mAccount.getStatus() == Account.State.SERVER_NOT_FOUND + && this.mHostname.getText().length() > 0) { + errorTextField = this.mHostname; + } else { + errorTextField = this.mAccountJid; + } + errorTextField.setError(getString(this.mAccount.getStatus().getReadableId())); + if (init || !accountInfoEdited()) { + errorTextField.requestFocus(); + } + } else { + this.mAccountJid.setError(null); + this.mPassword.setError(null); + this.mHostname.setError(null); + } + this.mStats.setVisibility(View.GONE); + } + } + + private void showOsOptimizationWarning(boolean showBatteryWarning, boolean showDataSaverWarning) { + this.mOsOptimizations.setVisibility(showBatteryWarning || showDataSaverWarning ? View.VISIBLE : View.GONE); + if (showDataSaverWarning) { + this.mDisableOsOptimizationsHeadline.setText(R.string.data_saver_enabled); + this.getmDisableOsOptimizationsBody.setText(R.string.data_saver_enabled_explained); + this.mDisableOsOptimizationsButton.setText(R.string.allow); + this.mDisableOsOptimizationsButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(Settings.ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS); + Uri uri = Uri.parse("package:" + getPackageName()); + intent.setData(uri); + try { + startActivityForResult(intent, REQUEST_DATA_SAVER); + } catch (ActivityNotFoundException e) { + Toast.makeText(EditAccountActivity.this, R.string.device_does_not_support_data_saver, Toast.LENGTH_SHORT).show(); + } + } + }); + } else if (showBatteryWarning) { + this.mDisableOsOptimizationsButton.setText(R.string.disable); + this.mDisableOsOptimizationsHeadline.setText(R.string.battery_optimizations_enabled); + this.getmDisableOsOptimizationsBody.setText(R.string.battery_optimizations_enabled_explained); + this.mDisableOsOptimizationsButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); + Uri uri = Uri.parse("package:" + getPackageName()); + intent.setData(uri); + try { + startActivityForResult(intent, REQUEST_BATTERY_OP); + } catch (ActivityNotFoundException e) { + Toast.makeText(EditAccountActivity.this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show(); + } + } + }); + } + } + + + public void showRegenerateAxolotlKeyDialog() { + Builder builder = new Builder(this); + builder.setTitle("Regenerate Key"); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + builder.setMessage("Are you sure you want to regenerate your Identity Key? (This will also wipe all established sessions and contact Identity Keys)"); + builder.setNegativeButton(getString(R.string.cancel), null); + builder.setPositiveButton("Yes", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mAccount.getAxolotlService().regenerateKeys(false); + } + }); + builder.create().show(); + } + + public void showWipePepDialog() { + Builder builder = new Builder(this); + builder.setTitle(getString(R.string.clear_other_devices)); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + builder.setMessage(getString(R.string.clear_other_devices_desc)); + builder.setNegativeButton(getString(R.string.cancel), null); + builder.setPositiveButton(getString(R.string.accept), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mAccount.getAxolotlService().wipeOtherPepDevices(); + } + }); + builder.create().show(); + } + + private void editMamPrefs() { + this.mFetchingMamPrefsToast = Toast.makeText(this, R.string.fetching_mam_prefs, Toast.LENGTH_LONG); + this.mFetchingMamPrefsToast.show(); + xmppConnectionService.fetchMamPreferences(mAccount, this); + } + + private void showPassword() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + View view = getLayoutInflater().inflate(R.layout.dialog_show_password, null); + TextView password = (TextView) view.findViewById(R.id.password); + password.setText(mAccount.getPassword()); + builder.setTitle(R.string.password); + builder.setView(view); + builder.setPositiveButton(R.string.cancel, null); + builder.create().show(); + } + + @Override + public void onKeyStatusUpdated(AxolotlService.FetchStatus report) { + refreshUi(); + } + + @Override + public void onCaptchaRequested(final Account account, final String id, final Data data, final Bitmap captcha) { + runOnUiThread(new Runnable() { + @Override + public void run() { + if ((mCaptchaDialog != null) && mCaptchaDialog.isShowing()) { + mCaptchaDialog.dismiss(); + } + final AlertDialog.Builder builder = new AlertDialog.Builder(EditAccountActivity.this); + final View view = getLayoutInflater().inflate(R.layout.captcha, null); + final ImageView imageView = (ImageView) view.findViewById(R.id.captcha); + final EditText input = (EditText) view.findViewById(R.id.input); + imageView.setImageBitmap(captcha); + + builder.setTitle(getString(R.string.captcha_required)); + builder.setView(view); + + builder.setPositiveButton(getString(R.string.ok), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String rc = input.getText().toString(); + data.put("username", account.getUsername()); + data.put("password", account.getPassword()); + data.put("ocr", rc); + data.submit(); + + if (xmppConnectionServiceBound) { + xmppConnectionService.sendCreateAccountWithCaptchaPacket( + account, id, data); + } + } + }); + builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (xmppConnectionService != null) { + xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, null, null); + } + } + }); + + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + if (xmppConnectionService != null) { + xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, null, null); + } + } + }); + mCaptchaDialog = builder.create(); + mCaptchaDialog.show(); + } + }); + } + + public void onShowErrorToast(final int resId) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(EditAccountActivity.this, resId, Toast.LENGTH_SHORT).show(); + } + }); + } + + @Override + public void onPreferencesFetched(final Element prefs) { + runOnUiThread(new Runnable() { + @Override + public void run() { + if (mFetchingMamPrefsToast != null) { + mFetchingMamPrefsToast.cancel(); + } + AlertDialog.Builder builder = new Builder(EditAccountActivity.this); + builder.setTitle(R.string.server_side_mam_prefs); + String defaultAttr = prefs.getAttribute("default"); + final List defaults = Arrays.asList("never", "roster", "always"); + final AtomicInteger choice = new AtomicInteger(Math.max(0, defaults.indexOf(defaultAttr))); + builder.setSingleChoiceItems(R.array.mam_prefs, choice.get(), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + choice.set(which); + } + }); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + prefs.setAttribute("default", defaults.get(choice.get())); + xmppConnectionService.pushMamPreferences(mAccount, prefs); + } + }); + builder.create().show(); + } + }); + } + + @Override + public void onPreferencesFetchFailed() { + runOnUiThread(new Runnable() { + @Override + public void run() { + if (mFetchingMamPrefsToast != null) { + mFetchingMamPrefsToast.cancel(); + } + Toast.makeText(EditAccountActivity.this, R.string.unable_to_fetch_mam_prefs, Toast.LENGTH_LONG).show(); + } + }); + } } \ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/EditMessage.java b/src/main/java/de/pixart/messenger/ui/EditMessage.java index ea56e4d56..8c0a7f934 100644 --- a/src/main/java/de/pixart/messenger/ui/EditMessage.java +++ b/src/main/java/de/pixart/messenger/ui/EditMessage.java @@ -14,84 +14,89 @@ import github.ankushsachdeva.emojicon.EmojiconEditText; public class EditMessage extends EmojiconEditText { - public EditMessage(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public EditMessage(Context context) { - super(context); - } - - protected Handler mTypingHandler = new Handler(); - - protected Runnable mTypingTimeout = new Runnable() { - @Override - public void run() { - if (isUserTyping && keyboardListener != null) { - keyboardListener.onTypingStopped(); - isUserTyping = false; - } - } - }; - - private boolean isUserTyping = false; - - private boolean lastInputWasTab = false; - - protected KeyboardListener keyboardListener; - - @Override - public boolean onKeyDown(int keyCode, KeyEvent e) { - if (keyCode == KeyEvent.KEYCODE_ENTER && !e.isShiftPressed()) { - lastInputWasTab = false; - if (keyboardListener != null && keyboardListener.onEnterPressed()) { - return true; - } - } else if (keyCode == KeyEvent.KEYCODE_TAB && !e.isAltPressed() && !e.isCtrlPressed()) { - if (keyboardListener != null && keyboardListener.onTabPressed(this.lastInputWasTab)) { - lastInputWasTab = true; - return true; - } - } else { - lastInputWasTab = false; - } - return super.onKeyDown(keyCode, e); - } - - @Override - public void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { - super.onTextChanged(text,start,lengthBefore,lengthAfter); - lastInputWasTab = false; - if (this.mTypingHandler != null && this.keyboardListener != null) { - this.mTypingHandler.removeCallbacks(mTypingTimeout); - this.mTypingHandler.postDelayed(mTypingTimeout, Config.TYPING_TIMEOUT * 1000); - final int length = text.length(); - if (!isUserTyping && length > 0) { - this.isUserTyping = true; - this.keyboardListener.onTypingStarted(); - } else if (length == 0) { - this.isUserTyping = false; - this.keyboardListener.onTextDeleted(); - } - this.keyboardListener.onTextChanged(); - } - } - - public void setKeyboardListener(KeyboardListener listener) { - this.keyboardListener = listener; - if (listener != null) { - this.isUserTyping = false; - } - } - - public interface KeyboardListener { - boolean onEnterPressed(); - void onTypingStarted(); - void onTypingStopped(); - void onTextDeleted(); - void onTextChanged(); - boolean onTabPressed(boolean repeated); - } + public EditMessage(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public EditMessage(Context context) { + super(context); + } + + protected Handler mTypingHandler = new Handler(); + + protected Runnable mTypingTimeout = new Runnable() { + @Override + public void run() { + if (isUserTyping && keyboardListener != null) { + keyboardListener.onTypingStopped(); + isUserTyping = false; + } + } + }; + + private boolean isUserTyping = false; + + private boolean lastInputWasTab = false; + + protected KeyboardListener keyboardListener; + + @Override + public boolean onKeyDown(int keyCode, KeyEvent e) { + if (keyCode == KeyEvent.KEYCODE_ENTER && !e.isShiftPressed()) { + lastInputWasTab = false; + if (keyboardListener != null && keyboardListener.onEnterPressed()) { + return true; + } + } else if (keyCode == KeyEvent.KEYCODE_TAB && !e.isAltPressed() && !e.isCtrlPressed()) { + if (keyboardListener != null && keyboardListener.onTabPressed(this.lastInputWasTab)) { + lastInputWasTab = true; + return true; + } + } else { + lastInputWasTab = false; + } + return super.onKeyDown(keyCode, e); + } + + @Override + public void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { + super.onTextChanged(text, start, lengthBefore, lengthAfter); + lastInputWasTab = false; + if (this.mTypingHandler != null && this.keyboardListener != null) { + this.mTypingHandler.removeCallbacks(mTypingTimeout); + this.mTypingHandler.postDelayed(mTypingTimeout, Config.TYPING_TIMEOUT * 1000); + final int length = text.length(); + if (!isUserTyping && length > 0) { + this.isUserTyping = true; + this.keyboardListener.onTypingStarted(); + } else if (length == 0) { + this.isUserTyping = false; + this.keyboardListener.onTextDeleted(); + } + this.keyboardListener.onTextChanged(); + } + } + + public void setKeyboardListener(KeyboardListener listener) { + this.keyboardListener = listener; + if (listener != null) { + this.isUserTyping = false; + } + } + + public interface KeyboardListener { + boolean onEnterPressed(); + + void onTypingStarted(); + + void onTypingStopped(); + + void onTextDeleted(); + + void onTextChanged(); + + boolean onTabPressed(boolean repeated); + } private static final InputFilter SPAN_FILTER = new InputFilter() { diff --git a/src/main/java/de/pixart/messenger/ui/EnterJidDialog.java b/src/main/java/de/pixart/messenger/ui/EnterJidDialog.java index 8f355e348..b26019fac 100644 --- a/src/main/java/de/pixart/messenger/ui/EnterJidDialog.java +++ b/src/main/java/de/pixart/messenger/ui/EnterJidDialog.java @@ -19,111 +19,111 @@ import de.pixart.messenger.xmpp.jid.InvalidJidException; import de.pixart.messenger.xmpp.jid.Jid; public class EnterJidDialog { - public interface OnEnterJidDialogPositiveListener { - boolean onEnterJidDialogPositive(Jid account, Jid contact) throws EnterJidDialog.JidError; - } - - public static class JidError extends Exception { - final String msg; - - public JidError(final String msg) { - this.msg = msg; - } - - public String toString() { - return msg; - } - } - - protected final AlertDialog dialog; - protected View.OnClickListener dialogOnClick; - protected OnEnterJidDialogPositiveListener listener = null; - - public EnterJidDialog( - final Context context, List knownHosts, final List activatedAccounts, - final String title, final String positiveButton, - final String prefilledJid, final String account, boolean allowEditJid - ) { - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(title); - View dialogView = LayoutInflater.from(context).inflate(R.layout.enter_jid_dialog, null); - final TextView jabberIdDesc = (TextView) dialogView.findViewById(R.id.jabber_id); - jabberIdDesc.setText(R.string.account_settings_jabber_id); - final Spinner spinner = (Spinner) dialogView.findViewById(R.id.account); - final AutoCompleteTextView jid = (AutoCompleteTextView) dialogView.findViewById(R.id.jid); - jid.setAdapter(new KnownHostsAdapter(context, R.layout.simple_list_item, knownHosts)); - if (prefilledJid != null) { - jid.append(prefilledJid); - if (!allowEditJid) { - jid.setFocusable(false); - jid.setFocusableInTouchMode(false); - jid.setClickable(false); - jid.setCursorVisible(false); - } - } - - jid.setHint(R.string.account_settings_example_jabber_id); - - if (account == null) { - StartConversationActivity.populateAccountSpinner(context, activatedAccounts, spinner); - } else { - ArrayAdapter adapter = new ArrayAdapter<>(context, - R.layout.simple_list_item, - new String[] { account }); - spinner.setEnabled(false); - adapter.setDropDownViewResource(R.layout.simple_list_item); - spinner.setAdapter(adapter); - } - - builder.setView(dialogView); - builder.setNegativeButton(R.string.cancel, null); - builder.setPositiveButton(positiveButton, null); - this.dialog = builder.create(); - - this.dialogOnClick = new View.OnClickListener() { - @Override - public void onClick(final View v) { - final Jid accountJid; - if (!spinner.isEnabled() && account == null) { - return; - } - try { - if (Config.DOMAIN_LOCK != null) { - accountJid = Jid.fromParts((String) spinner.getSelectedItem(), Config.DOMAIN_LOCK, null); - } else { - accountJid = Jid.fromString((String) spinner.getSelectedItem()); - } - } catch (final InvalidJidException e) { - return; - } - final Jid contactJid; - try { - contactJid = Jid.fromString(jid.getText().toString()); - } catch (final InvalidJidException e) { - jid.setError(context.getString(R.string.invalid_jid)); - return; - } - - if(listener != null) { - try { - if(listener.onEnterJidDialogPositive(accountJid, contactJid)) { - dialog.dismiss(); - } - } catch(JidError error) { - jid.setError(error.toString()); - } - } - } - }; - } - - public void setOnEnterJidDialogPositiveListener(OnEnterJidDialogPositiveListener listener) { - this.listener = listener; - } - - public Dialog show() { - this.dialog.show(); - this.dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(this.dialogOnClick); - return this.dialog; - } + public interface OnEnterJidDialogPositiveListener { + boolean onEnterJidDialogPositive(Jid account, Jid contact) throws EnterJidDialog.JidError; + } + + public static class JidError extends Exception { + final String msg; + + public JidError(final String msg) { + this.msg = msg; + } + + public String toString() { + return msg; + } + } + + protected final AlertDialog dialog; + protected View.OnClickListener dialogOnClick; + protected OnEnterJidDialogPositiveListener listener = null; + + public EnterJidDialog( + final Context context, List knownHosts, final List activatedAccounts, + final String title, final String positiveButton, + final String prefilledJid, final String account, boolean allowEditJid + ) { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(title); + View dialogView = LayoutInflater.from(context).inflate(R.layout.enter_jid_dialog, null); + final TextView jabberIdDesc = (TextView) dialogView.findViewById(R.id.jabber_id); + jabberIdDesc.setText(R.string.account_settings_jabber_id); + final Spinner spinner = (Spinner) dialogView.findViewById(R.id.account); + final AutoCompleteTextView jid = (AutoCompleteTextView) dialogView.findViewById(R.id.jid); + jid.setAdapter(new KnownHostsAdapter(context, R.layout.simple_list_item, knownHosts)); + if (prefilledJid != null) { + jid.append(prefilledJid); + if (!allowEditJid) { + jid.setFocusable(false); + jid.setFocusableInTouchMode(false); + jid.setClickable(false); + jid.setCursorVisible(false); + } + } + + jid.setHint(R.string.account_settings_example_jabber_id); + + if (account == null) { + StartConversationActivity.populateAccountSpinner(context, activatedAccounts, spinner); + } else { + ArrayAdapter adapter = new ArrayAdapter<>(context, + R.layout.simple_list_item, + new String[]{account}); + spinner.setEnabled(false); + adapter.setDropDownViewResource(R.layout.simple_list_item); + spinner.setAdapter(adapter); + } + + builder.setView(dialogView); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(positiveButton, null); + this.dialog = builder.create(); + + this.dialogOnClick = new View.OnClickListener() { + @Override + public void onClick(final View v) { + final Jid accountJid; + if (!spinner.isEnabled() && account == null) { + return; + } + try { + if (Config.DOMAIN_LOCK != null) { + accountJid = Jid.fromParts((String) spinner.getSelectedItem(), Config.DOMAIN_LOCK, null); + } else { + accountJid = Jid.fromString((String) spinner.getSelectedItem()); + } + } catch (final InvalidJidException e) { + return; + } + final Jid contactJid; + try { + contactJid = Jid.fromString(jid.getText().toString()); + } catch (final InvalidJidException e) { + jid.setError(context.getString(R.string.invalid_jid)); + return; + } + + if (listener != null) { + try { + if (listener.onEnterJidDialogPositive(accountJid, contactJid)) { + dialog.dismiss(); + } + } catch (JidError error) { + jid.setError(error.toString()); + } + } + } + }; + } + + public void setOnEnterJidDialogPositiveListener(OnEnterJidDialogPositiveListener listener) { + this.listener = listener; + } + + public Dialog show() { + this.dialog.show(); + this.dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(this.dialogOnClick); + return this.dialog; + } } diff --git a/src/main/java/de/pixart/messenger/ui/MagicCreateActivity.java b/src/main/java/de/pixart/messenger/ui/MagicCreateActivity.java index c83318160..5e41ad326 100644 --- a/src/main/java/de/pixart/messenger/ui/MagicCreateActivity.java +++ b/src/main/java/de/pixart/messenger/ui/MagicCreateActivity.java @@ -21,100 +21,100 @@ import de.pixart.messenger.xmpp.jid.Jid; public class MagicCreateActivity extends XmppActivity implements TextWatcher { - private TextView mFullJidDisplay; - private EditText mUsername; - private SecureRandom mRandom; - - private static final String CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456780+-/#$!?"; - private static final int PW_LENGTH = 10; - - @Override - protected void refreshUiReal() { - - } - - @Override - void onBackendConnected() { - - } - - @Override - protected void onCreate(final Bundle savedInstanceState) { - if (getResources().getBoolean(R.bool.portrait_only)) { - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); - } - super.onCreate(savedInstanceState); - setContentView(R.layout.magic_create); - mFullJidDisplay = (TextView) findViewById(R.id.full_jid); - mUsername = (EditText) findViewById(R.id.username); - mRandom = new SecureRandom(); - Button next = (Button) findViewById(R.id.create_account); - next.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - String username = mUsername.getText().toString(); - if (username.contains("@") || username.length() < 3) { - mUsername.setError(getString(R.string.invalid_username)); - mUsername.requestFocus(); - } else { - mUsername.setError(null); - try { - Jid jid = Jid.fromParts(username.toLowerCase(), Config.MAGIC_CREATE_DOMAIN, null); - Account account = xmppConnectionService.findAccountByJid(jid); - if (account == null) { - account = new Account(jid, createPassword()); - account.setOption(Account.OPTION_REGISTER, true); - account.setOption(Account.OPTION_DISABLED, true); - account.setOption(Account.OPTION_MAGIC_CREATE, true); - xmppConnectionService.createAccount(account); - } - Intent intent = new Intent(MagicCreateActivity.this, EditAccountActivity.class); - intent.putExtra("jid", account.getJid().toBareJid().toString()); - intent.putExtra("init", true); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - Toast.makeText(MagicCreateActivity.this, R.string.secure_password_generated, Toast.LENGTH_SHORT).show(); - startActivity(intent); - } catch (InvalidJidException e) { - mUsername.setError(getString(R.string.invalid_username)); - mUsername.requestFocus(); - } - } - } - }); - mUsername.addTextChangedListener(this); - } - - private String createPassword() { - StringBuilder builder = new StringBuilder(PW_LENGTH); - for(int i = 0; i < PW_LENGTH; ++i) { - builder.append(CHARS.charAt(mRandom.nextInt(CHARS.length() - 1))); - } - return builder.toString(); - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - - } - - @Override - public void afterTextChanged(Editable s) { - if (s.toString().trim().length() > 0) { - try { - mFullJidDisplay.setVisibility(View.VISIBLE); - Jid jid = Jid.fromParts(s.toString().toLowerCase(), Config.MAGIC_CREATE_DOMAIN, null); - mFullJidDisplay.setText(getString(R.string.your_full_jid_will_be, jid.toString())); - } catch (InvalidJidException e) { - mFullJidDisplay.setVisibility(View.INVISIBLE); - } - - } else { - mFullJidDisplay.setVisibility(View.INVISIBLE); - } - } + private TextView mFullJidDisplay; + private EditText mUsername; + private SecureRandom mRandom; + + private static final String CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456780+-/#$!?"; + private static final int PW_LENGTH = 10; + + @Override + protected void refreshUiReal() { + + } + + @Override + void onBackendConnected() { + + } + + @Override + protected void onCreate(final Bundle savedInstanceState) { + if (getResources().getBoolean(R.bool.portrait_only)) { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + } + super.onCreate(savedInstanceState); + setContentView(R.layout.magic_create); + mFullJidDisplay = (TextView) findViewById(R.id.full_jid); + mUsername = (EditText) findViewById(R.id.username); + mRandom = new SecureRandom(); + Button next = (Button) findViewById(R.id.create_account); + next.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String username = mUsername.getText().toString(); + if (username.contains("@") || username.length() < 3) { + mUsername.setError(getString(R.string.invalid_username)); + mUsername.requestFocus(); + } else { + mUsername.setError(null); + try { + Jid jid = Jid.fromParts(username.toLowerCase(), Config.MAGIC_CREATE_DOMAIN, null); + Account account = xmppConnectionService.findAccountByJid(jid); + if (account == null) { + account = new Account(jid, createPassword()); + account.setOption(Account.OPTION_REGISTER, true); + account.setOption(Account.OPTION_DISABLED, true); + account.setOption(Account.OPTION_MAGIC_CREATE, true); + xmppConnectionService.createAccount(account); + } + Intent intent = new Intent(MagicCreateActivity.this, EditAccountActivity.class); + intent.putExtra("jid", account.getJid().toBareJid().toString()); + intent.putExtra("init", true); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + Toast.makeText(MagicCreateActivity.this, R.string.secure_password_generated, Toast.LENGTH_SHORT).show(); + startActivity(intent); + } catch (InvalidJidException e) { + mUsername.setError(getString(R.string.invalid_username)); + mUsername.requestFocus(); + } + } + } + }); + mUsername.addTextChangedListener(this); + } + + private String createPassword() { + StringBuilder builder = new StringBuilder(PW_LENGTH); + for (int i = 0; i < PW_LENGTH; ++i) { + builder.append(CHARS.charAt(mRandom.nextInt(CHARS.length() - 1))); + } + return builder.toString(); + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + if (s.toString().trim().length() > 0) { + try { + mFullJidDisplay.setVisibility(View.VISIBLE); + Jid jid = Jid.fromParts(s.toString().toLowerCase(), Config.MAGIC_CREATE_DOMAIN, null); + mFullJidDisplay.setText(getString(R.string.your_full_jid_will_be, jid.toString())); + } catch (InvalidJidException e) { + mFullJidDisplay.setVisibility(View.INVISIBLE); + } + + } else { + mFullJidDisplay.setVisibility(View.INVISIBLE); + } + } } diff --git a/src/main/java/de/pixart/messenger/ui/ManageAccountActivity.java b/src/main/java/de/pixart/messenger/ui/ManageAccountActivity.java index 0eb5eda21..66d1330ef 100644 --- a/src/main/java/de/pixart/messenger/ui/ManageAccountActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ManageAccountActivity.java @@ -38,69 +38,69 @@ import de.pixart.messenger.xmpp.jid.Jid; public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate, KeyChainAliasCallback, XmppConnectionService.OnAccountCreated { - private final String STATE_SELECTED_ACCOUNT = "selected_account"; - - protected Account selectedAccount = null; - protected Jid selectedAccountJid = null; - - protected final List accountList = new ArrayList<>(); - protected ListView accountListView; - protected AccountAdapter mAccountAdapter; - protected AtomicBoolean mInvokedAddAccount = new AtomicBoolean(false); - - protected Pair mPostponedActivityResult = null; - - @Override - public void onAccountUpdate() { - refreshUi(); - } - - @Override - protected void refreshUiReal() { - synchronized (this.accountList) { - accountList.clear(); - accountList.addAll(xmppConnectionService.getAccounts()); - } - ActionBar actionBar = getActionBar(); - if (actionBar != null) { - actionBar.setHomeButtonEnabled(this.accountList.size() > 0); - actionBar.setDisplayHomeAsUpEnabled(this.accountList.size() > 0); - } - invalidateOptionsMenu(); - mAccountAdapter.notifyDataSetChanged(); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - - super.onCreate(savedInstanceState); - - setContentView(R.layout.manage_accounts); - - if (savedInstanceState != null) { - String jid = savedInstanceState.getString(STATE_SELECTED_ACCOUNT); - if (jid != null) { - try { - this.selectedAccountJid = Jid.fromString(jid); - } catch (InvalidJidException e) { - this.selectedAccountJid = null; - } - } - } - - accountListView = (ListView) findViewById(R.id.account_list); - this.mAccountAdapter = new AccountAdapter(this, accountList); - accountListView.setAdapter(this.mAccountAdapter); - accountListView.setOnItemClickListener(new OnItemClickListener() { - - @Override - public void onItemClick(AdapterView arg0, View view, - int position, long arg3) { - switchToAccount(accountList.get(position)); - } - }); - registerForContextMenu(accountListView); - } + private final String STATE_SELECTED_ACCOUNT = "selected_account"; + + protected Account selectedAccount = null; + protected Jid selectedAccountJid = null; + + protected final List accountList = new ArrayList<>(); + protected ListView accountListView; + protected AccountAdapter mAccountAdapter; + protected AtomicBoolean mInvokedAddAccount = new AtomicBoolean(false); + + protected Pair mPostponedActivityResult = null; + + @Override + public void onAccountUpdate() { + refreshUi(); + } + + @Override + protected void refreshUiReal() { + synchronized (this.accountList) { + accountList.clear(); + accountList.addAll(xmppConnectionService.getAccounts()); + } + ActionBar actionBar = getActionBar(); + if (actionBar != null) { + actionBar.setHomeButtonEnabled(this.accountList.size() > 0); + actionBar.setDisplayHomeAsUpEnabled(this.accountList.size() > 0); + } + invalidateOptionsMenu(); + mAccountAdapter.notifyDataSetChanged(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + + super.onCreate(savedInstanceState); + + setContentView(R.layout.manage_accounts); + + if (savedInstanceState != null) { + String jid = savedInstanceState.getString(STATE_SELECTED_ACCOUNT); + if (jid != null) { + try { + this.selectedAccountJid = Jid.fromString(jid); + } catch (InvalidJidException e) { + this.selectedAccountJid = null; + } + } + } + + accountListView = (ListView) findViewById(R.id.account_list); + this.mAccountAdapter = new AccountAdapter(this, accountList); + accountListView.setAdapter(this.mAccountAdapter); + accountListView.setOnItemClickListener(new OnItemClickListener() { + + @Override + public void onItemClick(AdapterView arg0, View view, + int position, long arg3) { + switchToAccount(accountList.get(position)); + } + }); + registerForContextMenu(accountListView); + } @Override protected void onStart() { @@ -111,286 +111,286 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda } } - @Override - public void onSaveInstanceState(final Bundle savedInstanceState) { - if (selectedAccount != null) { - savedInstanceState.putString(STATE_SELECTED_ACCOUNT, selectedAccount.getJid().toBareJid().toString()); - } - super.onSaveInstanceState(savedInstanceState); - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - ManageAccountActivity.this.getMenuInflater().inflate( + @Override + public void onSaveInstanceState(final Bundle savedInstanceState) { + if (selectedAccount != null) { + savedInstanceState.putString(STATE_SELECTED_ACCOUNT, selectedAccount.getJid().toBareJid().toString()); + } + super.onSaveInstanceState(savedInstanceState); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + ManageAccountActivity.this.getMenuInflater().inflate( R.menu.manageaccounts_context, menu); - AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo; - this.selectedAccount = accountList.get(acmi.position); - if (this.selectedAccount.isOptionSet(Account.OPTION_DISABLED)) { - menu.findItem(R.id.mgmt_account_reconnect).setVisible(false); - menu.findItem(R.id.mgmt_account_announce_pgp).setVisible(false); - menu.findItem(R.id.mgmt_account_publish_avatar).setVisible(false); - menu.findItem(R.id.mgmt_account_change_presence).setVisible(false); - } else { - menu.findItem(R.id.mgmt_account_announce_pgp).setVisible(Config.supportOpenPgp()); - menu.findItem(R.id.mgmt_account_change_presence).setVisible(manuallyChangePresence()); - } - menu.setHeaderTitle(this.selectedAccount.getJid().toBareJid().toString()); - } - - @Override - void onBackendConnected() { - if (selectedAccountJid != null) { - this.selectedAccount = xmppConnectionService.findAccountByJid(selectedAccountJid); - } - refreshUiReal(); - if (this.mPostponedActivityResult != null) { - this.onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second); - } - if (Config.X509_VERIFICATION && this.accountList.size() == 0) { - if (mInvokedAddAccount.compareAndSet(false, true)) { - addAccountFromKey(); - } - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.manageaccounts, menu); - MenuItem addAccount = menu.findItem(R.id.action_add_account); - MenuItem addAccountWithCertificate = menu.findItem(R.id.action_add_account_with_cert); - - if (Config.X509_VERIFICATION) { - addAccount.setVisible(false); - addAccountWithCertificate.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); - } else { - addAccount.setVisible(!Config.SINGLE_ACCOUNT); - } - addAccountWithCertificate.setVisible(!Config.SINGLE_ACCOUNT); - return true; - } - - @Override - public boolean onContextItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.mgmt_account_publish_avatar: - publishAvatar(selectedAccount); - return true; - case R.id.mgmt_account_reconnect: - disableAccount(selectedAccount); - enableAccount(selectedAccount); - return true; - case R.id.mgmt_account_delete: - deleteAccount(selectedAccount); - return true; - case R.id.mgmt_account_announce_pgp: - publishOpenPGPPublicKey(selectedAccount); - return true; - case R.id.mgmt_account_change_presence: - changePresence(selectedAccount); - return true; - default: - return super.onContextItemSelected(item); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.action_add_account: - startActivity(new Intent(getApplicationContext(), - EditAccountActivity.class)); - break; - case R.id.action_add_account_with_cert: - addAccountFromKey(); - break; - default: - break; - } - return super.onOptionsItemSelected(item); - } - - @Override - public boolean onNavigateUp() { - if (xmppConnectionService.getConversations().size() == 0) { - Intent contactsIntent = new Intent(this, - StartConversationActivity.class); - contactsIntent.setFlags( - // if activity exists in stack, pop the stack and go back to it - Intent.FLAG_ACTIVITY_CLEAR_TOP | - // otherwise, make a new task for it - Intent.FLAG_ACTIVITY_NEW_TASK | - // don't use the new activity animation; finish - // animation runs instead - Intent.FLAG_ACTIVITY_NO_ANIMATION); - startActivity(contactsIntent); - finish(); - return true; - } else { - return super.onNavigateUp(); - } - } - - private void changePresence(Account account) { - Intent intent = new Intent(this, SetPresenceActivity.class); - intent.putExtra(SetPresenceActivity.EXTRA_ACCOUNT,account.getJid().toBareJid().toString()); - startActivity(intent); - } - - public void onClickTglAccountState(Account account, boolean enable) { - if (enable) { - enableAccount(account); - } else { - disableAccount(account); - } - } - - private void addAccountFromKey() { - try { - KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null); - } catch (ActivityNotFoundException e) { - Toast.makeText(this, R.string.device_does_not_support_certificates, Toast.LENGTH_LONG).show(); - } - } - - private void publishAvatar(Account account) { - Intent intent = new Intent(getApplicationContext(), - PublishProfilePictureActivity.class); - intent.putExtra(EXTRA_ACCOUNT, account.getJid().toString()); - startActivity(intent); - } - - private void disableAllAccounts() { - List list = new ArrayList<>(); - synchronized (this.accountList) { - for (Account account : this.accountList) { - if (!account.isOptionSet(Account.OPTION_DISABLED)) { - list.add(account); - } - } - } - for (Account account : list) { - disableAccount(account); - } - } - - private boolean accountsLeftToDisable() { - synchronized (this.accountList) { - for (Account account : this.accountList) { - if (!account.isOptionSet(Account.OPTION_DISABLED)) { - return true; - } - } - return false; - } - } - - private boolean accountsLeftToEnable() { - synchronized (this.accountList) { - for (Account account : this.accountList) { - if (account.isOptionSet(Account.OPTION_DISABLED)) { - return true; - } - } - return false; - } - } - - private void enableAllAccounts() { - List list = new ArrayList<>(); - synchronized (this.accountList) { - for (Account account : this.accountList) { - if (account.isOptionSet(Account.OPTION_DISABLED)) { - list.add(account); - } - } - } - for (Account account : list) { - enableAccount(account); - } - } - - private void disableAccount(Account account) { - account.setOption(Account.OPTION_DISABLED, true); - if (!xmppConnectionService.updateAccount(account)) { - Toast.makeText(this,R.string.unable_to_update_account,Toast.LENGTH_SHORT).show(); - } - } - - private void enableAccount(Account account) { - account.setOption(Account.OPTION_DISABLED, false); - if (!xmppConnectionService.updateAccount(account)) { - Toast.makeText(this,R.string.unable_to_update_account,Toast.LENGTH_SHORT).show(); - } - } - - private void publishOpenPGPPublicKey(Account account) { - if (ManageAccountActivity.this.hasPgp()) { - announcePgp(selectedAccount, null, onOpenPGPKeyPublished); - } else { - this.showInstallPgpDialog(); - } - } - - private void deleteAccount(final Account account) { - AlertDialog.Builder builder = new AlertDialog.Builder( - ManageAccountActivity.this); - builder.setTitle(getString(R.string.mgmt_account_are_you_sure)); - builder.setIconAttribute(android.R.attr.alertDialogIcon); - builder.setMessage(getString(R.string.mgmt_account_delete_confirm_text)); - builder.setPositiveButton(getString(R.string.delete), - new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - xmppConnectionService.deleteAccount(account); - selectedAccount = null; - } - }); - builder.setNegativeButton(getString(R.string.cancel), null); - builder.create().show(); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (resultCode == RESULT_OK) { - if (xmppConnectionServiceBound) { - if (requestCode == REQUEST_CHOOSE_PGP_ID) { - if (data.getExtras().containsKey(OpenPgpApi.EXTRA_SIGN_KEY_ID)) { - selectedAccount.setPgpSignId(data.getExtras().getLong(OpenPgpApi.EXTRA_SIGN_KEY_ID)); - announcePgp(selectedAccount, null, onOpenPGPKeyPublished); - } else { - choosePgpSignId(selectedAccount); - } - } else if (requestCode == REQUEST_ANNOUNCE_PGP) { - announcePgp(selectedAccount, null, onOpenPGPKeyPublished); - } - this.mPostponedActivityResult = null; - } else { - this.mPostponedActivityResult = new Pair<>(requestCode, data); - } - } - } - - @Override - public void alias(String alias) { - if (alias != null) { - xmppConnectionService.createAccountFromKey(alias, this); - } - } - - @Override - public void onAccountCreated(Account account) { - switchToAccount(account, true); - } - - @Override - public void informUser(final int r) { - runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(ManageAccountActivity.this, r, Toast.LENGTH_LONG).show(); - } - }); - } + AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo; + this.selectedAccount = accountList.get(acmi.position); + if (this.selectedAccount.isOptionSet(Account.OPTION_DISABLED)) { + menu.findItem(R.id.mgmt_account_reconnect).setVisible(false); + menu.findItem(R.id.mgmt_account_announce_pgp).setVisible(false); + menu.findItem(R.id.mgmt_account_publish_avatar).setVisible(false); + menu.findItem(R.id.mgmt_account_change_presence).setVisible(false); + } else { + menu.findItem(R.id.mgmt_account_announce_pgp).setVisible(Config.supportOpenPgp()); + menu.findItem(R.id.mgmt_account_change_presence).setVisible(manuallyChangePresence()); + } + menu.setHeaderTitle(this.selectedAccount.getJid().toBareJid().toString()); + } + + @Override + void onBackendConnected() { + if (selectedAccountJid != null) { + this.selectedAccount = xmppConnectionService.findAccountByJid(selectedAccountJid); + } + refreshUiReal(); + if (this.mPostponedActivityResult != null) { + this.onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second); + } + if (Config.X509_VERIFICATION && this.accountList.size() == 0) { + if (mInvokedAddAccount.compareAndSet(false, true)) { + addAccountFromKey(); + } + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.manageaccounts, menu); + MenuItem addAccount = menu.findItem(R.id.action_add_account); + MenuItem addAccountWithCertificate = menu.findItem(R.id.action_add_account_with_cert); + + if (Config.X509_VERIFICATION) { + addAccount.setVisible(false); + addAccountWithCertificate.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + } else { + addAccount.setVisible(!Config.SINGLE_ACCOUNT); + } + addAccountWithCertificate.setVisible(!Config.SINGLE_ACCOUNT); + return true; + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.mgmt_account_publish_avatar: + publishAvatar(selectedAccount); + return true; + case R.id.mgmt_account_reconnect: + disableAccount(selectedAccount); + enableAccount(selectedAccount); + return true; + case R.id.mgmt_account_delete: + deleteAccount(selectedAccount); + return true; + case R.id.mgmt_account_announce_pgp: + publishOpenPGPPublicKey(selectedAccount); + return true; + case R.id.mgmt_account_change_presence: + changePresence(selectedAccount); + return true; + default: + return super.onContextItemSelected(item); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_add_account: + startActivity(new Intent(getApplicationContext(), + EditAccountActivity.class)); + break; + case R.id.action_add_account_with_cert: + addAccountFromKey(); + break; + default: + break; + } + return super.onOptionsItemSelected(item); + } + + @Override + public boolean onNavigateUp() { + if (xmppConnectionService.getConversations().size() == 0) { + Intent contactsIntent = new Intent(this, + StartConversationActivity.class); + contactsIntent.setFlags( + // if activity exists in stack, pop the stack and go back to it + Intent.FLAG_ACTIVITY_CLEAR_TOP | + // otherwise, make a new task for it + Intent.FLAG_ACTIVITY_NEW_TASK | + // don't use the new activity animation; finish + // animation runs instead + Intent.FLAG_ACTIVITY_NO_ANIMATION); + startActivity(contactsIntent); + finish(); + return true; + } else { + return super.onNavigateUp(); + } + } + + private void changePresence(Account account) { + Intent intent = new Intent(this, SetPresenceActivity.class); + intent.putExtra(SetPresenceActivity.EXTRA_ACCOUNT, account.getJid().toBareJid().toString()); + startActivity(intent); + } + + public void onClickTglAccountState(Account account, boolean enable) { + if (enable) { + enableAccount(account); + } else { + disableAccount(account); + } + } + + private void addAccountFromKey() { + try { + KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null); + } catch (ActivityNotFoundException e) { + Toast.makeText(this, R.string.device_does_not_support_certificates, Toast.LENGTH_LONG).show(); + } + } + + private void publishAvatar(Account account) { + Intent intent = new Intent(getApplicationContext(), + PublishProfilePictureActivity.class); + intent.putExtra(EXTRA_ACCOUNT, account.getJid().toString()); + startActivity(intent); + } + + private void disableAllAccounts() { + List list = new ArrayList<>(); + synchronized (this.accountList) { + for (Account account : this.accountList) { + if (!account.isOptionSet(Account.OPTION_DISABLED)) { + list.add(account); + } + } + } + for (Account account : list) { + disableAccount(account); + } + } + + private boolean accountsLeftToDisable() { + synchronized (this.accountList) { + for (Account account : this.accountList) { + if (!account.isOptionSet(Account.OPTION_DISABLED)) { + return true; + } + } + return false; + } + } + + private boolean accountsLeftToEnable() { + synchronized (this.accountList) { + for (Account account : this.accountList) { + if (account.isOptionSet(Account.OPTION_DISABLED)) { + return true; + } + } + return false; + } + } + + private void enableAllAccounts() { + List list = new ArrayList<>(); + synchronized (this.accountList) { + for (Account account : this.accountList) { + if (account.isOptionSet(Account.OPTION_DISABLED)) { + list.add(account); + } + } + } + for (Account account : list) { + enableAccount(account); + } + } + + private void disableAccount(Account account) { + account.setOption(Account.OPTION_DISABLED, true); + if (!xmppConnectionService.updateAccount(account)) { + Toast.makeText(this, R.string.unable_to_update_account, Toast.LENGTH_SHORT).show(); + } + } + + private void enableAccount(Account account) { + account.setOption(Account.OPTION_DISABLED, false); + if (!xmppConnectionService.updateAccount(account)) { + Toast.makeText(this, R.string.unable_to_update_account, Toast.LENGTH_SHORT).show(); + } + } + + private void publishOpenPGPPublicKey(Account account) { + if (ManageAccountActivity.this.hasPgp()) { + announcePgp(selectedAccount, null, onOpenPGPKeyPublished); + } else { + this.showInstallPgpDialog(); + } + } + + private void deleteAccount(final Account account) { + AlertDialog.Builder builder = new AlertDialog.Builder( + ManageAccountActivity.this); + builder.setTitle(getString(R.string.mgmt_account_are_you_sure)); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + builder.setMessage(getString(R.string.mgmt_account_delete_confirm_text)); + builder.setPositiveButton(getString(R.string.delete), + new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + xmppConnectionService.deleteAccount(account); + selectedAccount = null; + } + }); + builder.setNegativeButton(getString(R.string.cancel), null); + builder.create().show(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode == RESULT_OK) { + if (xmppConnectionServiceBound) { + if (requestCode == REQUEST_CHOOSE_PGP_ID) { + if (data.getExtras().containsKey(OpenPgpApi.EXTRA_SIGN_KEY_ID)) { + selectedAccount.setPgpSignId(data.getExtras().getLong(OpenPgpApi.EXTRA_SIGN_KEY_ID)); + announcePgp(selectedAccount, null, onOpenPGPKeyPublished); + } else { + choosePgpSignId(selectedAccount); + } + } else if (requestCode == REQUEST_ANNOUNCE_PGP) { + announcePgp(selectedAccount, null, onOpenPGPKeyPublished); + } + this.mPostponedActivityResult = null; + } else { + this.mPostponedActivityResult = new Pair<>(requestCode, data); + } + } + } + + @Override + public void alias(String alias) { + if (alias != null) { + xmppConnectionService.createAccountFromKey(alias, this); + } + } + + @Override + public void onAccountCreated(Account account) { + switchToAccount(account, true); + } + + @Override + public void informUser(final int r) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(ManageAccountActivity.this, r, Toast.LENGTH_LONG).show(); + } + }); + } } diff --git a/src/main/java/de/pixart/messenger/ui/PublishProfilePictureActivity.java b/src/main/java/de/pixart/messenger/ui/PublishProfilePictureActivity.java index 423ccef9e..ac981f722 100644 --- a/src/main/java/de/pixart/messenger/ui/PublishProfilePictureActivity.java +++ b/src/main/java/de/pixart/messenger/ui/PublishProfilePictureActivity.java @@ -1,4 +1,3 @@ - package de.pixart.messenger.ui; import android.app.PendingIntent; @@ -31,311 +30,311 @@ import de.pixart.messenger.xmpp.pep.Avatar; public class PublishProfilePictureActivity extends XmppActivity { - private static final int REQUEST_CHOOSE_FILE_AND_CROP = 0xac23; - private static final int REQUEST_CHOOSE_FILE = 0xac24; - private ImageView avatar; - private TextView accountTextView; - private TextView hintOrWarning; - private TextView secondaryHint; - private Button cancelButton; - private Button publishButton; - private Uri avatarUri; - private Uri defaultUri; - private Account account; - private boolean support = false; - private OnLongClickListener backToDefaultListener = new OnLongClickListener() { + private static final int REQUEST_CHOOSE_FILE_AND_CROP = 0xac23; + private static final int REQUEST_CHOOSE_FILE = 0xac24; + private ImageView avatar; + private TextView accountTextView; + private TextView hintOrWarning; + private TextView secondaryHint; + private Button cancelButton; + private Button publishButton; + private Uri avatarUri; + private Uri defaultUri; + private Account account; + private boolean support = false; + private OnLongClickListener backToDefaultListener = new OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - avatarUri = defaultUri; - loadImageIntoPreview(defaultUri); - return true; - } - }; - private boolean mInitialAccountSetup; - private UiCallback avatarPublication = new UiCallback() { + @Override + public boolean onLongClick(View v) { + avatarUri = defaultUri; + loadImageIntoPreview(defaultUri); + return true; + } + }; + private boolean mInitialAccountSetup; + private UiCallback avatarPublication = new UiCallback() { - @Override - public void success(Avatar object) { - runOnUiThread(new Runnable() { + @Override + public void success(Avatar object) { + runOnUiThread(new Runnable() { - @Override - public void run() { - if (mInitialAccountSetup) { - Intent intent = new Intent(getApplicationContext(), - StartConversationActivity.class); - intent.putExtra("init", true); - startActivity(intent); - } - Toast.makeText(PublishProfilePictureActivity.this, - R.string.avatar_has_been_published, - Toast.LENGTH_SHORT).show(); - finish(); - } - }); - } + @Override + public void run() { + if (mInitialAccountSetup) { + Intent intent = new Intent(getApplicationContext(), + StartConversationActivity.class); + intent.putExtra("init", true); + startActivity(intent); + } + Toast.makeText(PublishProfilePictureActivity.this, + R.string.avatar_has_been_published, + Toast.LENGTH_SHORT).show(); + finish(); + } + }); + } - @Override - public void error(final int errorCode, Avatar object) { - runOnUiThread(new Runnable() { + @Override + public void error(final int errorCode, Avatar object) { + runOnUiThread(new Runnable() { - @Override - public void run() { - hintOrWarning.setText(errorCode); - hintOrWarning.setTextColor(getWarningTextColor()); - publishButton.setText(R.string.publish); - enablePublishButton(); - } - }); + @Override + public void run() { + hintOrWarning.setText(errorCode); + hintOrWarning.setTextColor(getWarningTextColor()); + publishButton.setText(R.string.publish); + enablePublishButton(); + } + }); - } + } - @Override - public void userInputRequried(PendingIntent pi, Avatar object) { - } - }; + @Override + public void userInputRequried(PendingIntent pi, Avatar object) { + } + }; - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_publish_profile_picture); - this.avatar = (ImageView) findViewById(R.id.account_image); - this.cancelButton = (Button) findViewById(R.id.cancel_button); - this.publishButton = (Button) findViewById(R.id.publish_button); - this.accountTextView = (TextView) findViewById(R.id.account); - this.hintOrWarning = (TextView) findViewById(R.id.hint_or_warning); - this.secondaryHint = (TextView) findViewById(R.id.secondary_hint); - this.publishButton.setOnClickListener(new OnClickListener() { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_publish_profile_picture); + this.avatar = (ImageView) findViewById(R.id.account_image); + this.cancelButton = (Button) findViewById(R.id.cancel_button); + this.publishButton = (Button) findViewById(R.id.publish_button); + this.accountTextView = (TextView) findViewById(R.id.account); + this.hintOrWarning = (TextView) findViewById(R.id.hint_or_warning); + this.secondaryHint = (TextView) findViewById(R.id.secondary_hint); + this.publishButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (avatarUri != null) { - publishButton.setText(R.string.publishing); - disablePublishButton(); - xmppConnectionService.publishAvatar(account, avatarUri, - avatarPublication); - } - } - }); - this.cancelButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (avatarUri != null) { + publishButton.setText(R.string.publishing); + disablePublishButton(); + xmppConnectionService.publishAvatar(account, avatarUri, + avatarPublication); + } + } + }); + this.cancelButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (mInitialAccountSetup) { - Intent intent = new Intent(getApplicationContext(), - StartConversationActivity.class); - if (xmppConnectionService != null && xmppConnectionService.getAccounts().size() == 1) { - intent.putExtra("init", true); - } - startActivity(intent); - } - finish(); - } - }); - this.avatar.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (mInitialAccountSetup) { + Intent intent = new Intent(getApplicationContext(), + StartConversationActivity.class); + if (xmppConnectionService != null && xmppConnectionService.getAccounts().size() == 1) { + intent.putExtra("init", true); + } + startActivity(intent); + } + finish(); + } + }); + this.avatar.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (hasStoragePermission(REQUEST_CHOOSE_FILE)) { - chooseAvatar(false); - } + @Override + public void onClick(View v) { + if (hasStoragePermission(REQUEST_CHOOSE_FILE)) { + chooseAvatar(false); + } - } - }); - this.defaultUri = PhoneHelper.getSelfiUri(getApplicationContext()); - } + } + }); + this.defaultUri = PhoneHelper.getSelfiUri(getApplicationContext()); + } - private void chooseAvatar(boolean crop) { - Intent attachFileIntent = new Intent(); - attachFileIntent.setType("image/*"); - attachFileIntent.setAction(Intent.ACTION_GET_CONTENT); - Intent chooser = Intent.createChooser(attachFileIntent, getString(R.string.attach_file)); - startActivityForResult(chooser, crop ? REQUEST_CHOOSE_FILE_AND_CROP : REQUEST_CHOOSE_FILE); - } + private void chooseAvatar(boolean crop) { + Intent attachFileIntent = new Intent(); + attachFileIntent.setType("image/*"); + attachFileIntent.setAction(Intent.ACTION_GET_CONTENT); + Intent chooser = Intent.createChooser(attachFileIntent, getString(R.string.attach_file)); + startActivityForResult(chooser, crop ? REQUEST_CHOOSE_FILE_AND_CROP : REQUEST_CHOOSE_FILE); + } - @Override - public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { - if (grantResults.length > 0) - if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { - if (requestCode == REQUEST_CHOOSE_FILE_AND_CROP) { - chooseAvatar(true); - } else if (requestCode == REQUEST_CHOOSE_FILE) { - chooseAvatar(false); - } - } else { - Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show(); - } - } + @Override + public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { + if (grantResults.length > 0) + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + if (requestCode == REQUEST_CHOOSE_FILE_AND_CROP) { + chooseAvatar(true); + } else if (requestCode == REQUEST_CHOOSE_FILE) { + chooseAvatar(false); + } + } else { + Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show(); + } + } - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.publish_avatar, menu); - return super.onCreateOptionsMenu(menu); - } + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.publish_avatar, menu); + return super.onCreateOptionsMenu(menu); + } - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - if (item.getItemId() == R.id.action_crop_image) { - if (hasStoragePermission(REQUEST_CHOOSE_FILE_AND_CROP)) { - chooseAvatar(true); - } - return true; - } else { - return super.onOptionsItemSelected(item); - } - } + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + if (item.getItemId() == R.id.action_crop_image) { + if (hasStoragePermission(REQUEST_CHOOSE_FILE_AND_CROP)) { + chooseAvatar(true); + } + return true; + } else { + return super.onOptionsItemSelected(item); + } + } - @Override - protected void onActivityResult(int requestCode, int resultCode, final Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (resultCode == RESULT_OK) { - Uri source = data.getData(); - switch (requestCode) { - case REQUEST_CHOOSE_FILE_AND_CROP: - if (FileBackend.weOwnFile(this, source)) { - Toast.makeText(this,R.string.security_error_invalid_file_access,Toast.LENGTH_SHORT).show(); - return; - } - String original = FileUtils.getPath(this, source); - if (original != null) { - source = Uri.parse("file://"+original); - } - Uri destination = Uri.fromFile(new File(getCacheDir(), "croppedAvatar")); - final int size = getPixel(Config.AVATAR_SIZE); - Crop.of(source, destination).asSquare().withMaxSize(size, size).start(this); - break; - case REQUEST_CHOOSE_FILE: - if (FileBackend.weOwnFile(this, source)) { - Toast.makeText(this,R.string.security_error_invalid_file_access,Toast.LENGTH_SHORT).show(); - return; - } - this.avatarUri = source; - if (xmppConnectionServiceBound) { - loadImageIntoPreview(this.avatarUri); - } - break; - case Crop.REQUEST_CROP: - this.avatarUri = Uri.fromFile(new File(getCacheDir(), "croppedAvatar")); - if (xmppConnectionServiceBound) { - loadImageIntoPreview(this.avatarUri); - } - break; - } - } else { - if (requestCode == Crop.REQUEST_CROP && data != null) { - Throwable throwable = Crop.getError(data); - if (throwable != null && throwable instanceof OutOfMemoryError) { - Toast.makeText(this,R.string.selection_too_large, Toast.LENGTH_SHORT).show(); - } - } - } - } + @Override + protected void onActivityResult(int requestCode, int resultCode, final Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode == RESULT_OK) { + Uri source = data.getData(); + switch (requestCode) { + case REQUEST_CHOOSE_FILE_AND_CROP: + if (FileBackend.weOwnFile(this, source)) { + Toast.makeText(this, R.string.security_error_invalid_file_access, Toast.LENGTH_SHORT).show(); + return; + } + String original = FileUtils.getPath(this, source); + if (original != null) { + source = Uri.parse("file://" + original); + } + Uri destination = Uri.fromFile(new File(getCacheDir(), "croppedAvatar")); + final int size = getPixel(Config.AVATAR_SIZE); + Crop.of(source, destination).asSquare().withMaxSize(size, size).start(this); + break; + case REQUEST_CHOOSE_FILE: + if (FileBackend.weOwnFile(this, source)) { + Toast.makeText(this, R.string.security_error_invalid_file_access, Toast.LENGTH_SHORT).show(); + return; + } + this.avatarUri = source; + if (xmppConnectionServiceBound) { + loadImageIntoPreview(this.avatarUri); + } + break; + case Crop.REQUEST_CROP: + this.avatarUri = Uri.fromFile(new File(getCacheDir(), "croppedAvatar")); + if (xmppConnectionServiceBound) { + loadImageIntoPreview(this.avatarUri); + } + break; + } + } else { + if (requestCode == Crop.REQUEST_CROP && data != null) { + Throwable throwable = Crop.getError(data); + if (throwable != null && throwable instanceof OutOfMemoryError) { + Toast.makeText(this, R.string.selection_too_large, Toast.LENGTH_SHORT).show(); + } + } + } + } - @Override - protected void onBackendConnected() { - this.account = extractAccount(getIntent()); - if (this.account != null) { - if (this.account.getXmppConnection() != null) { - this.support = this.account.getXmppConnection().getFeatures().pep(); - } - if (this.avatarUri == null) { - if (this.account.getAvatar() != null - || this.defaultUri == null) { - this.avatar.setImageBitmap(avatarService().get(account, getPixel(Config.AVATAR_SIZE))); - if (this.defaultUri != null) { - this.avatar - .setOnLongClickListener(this.backToDefaultListener); - } else { - this.secondaryHint.setVisibility(View.INVISIBLE); - } - if (!support) { - this.hintOrWarning - .setTextColor(getWarningTextColor()); + @Override + protected void onBackendConnected() { + this.account = extractAccount(getIntent()); + if (this.account != null) { + if (this.account.getXmppConnection() != null) { + this.support = this.account.getXmppConnection().getFeatures().pep(); + } + if (this.avatarUri == null) { + if (this.account.getAvatar() != null + || this.defaultUri == null) { + this.avatar.setImageBitmap(avatarService().get(account, getPixel(Config.AVATAR_SIZE))); + if (this.defaultUri != null) { + this.avatar + .setOnLongClickListener(this.backToDefaultListener); + } else { + this.secondaryHint.setVisibility(View.INVISIBLE); + } + if (!support) { + this.hintOrWarning + .setTextColor(getWarningTextColor()); if (account.getStatus() == Account.State.ONLINE) { this.hintOrWarning.setText(R.string.error_publish_avatar_no_server_support); } else { this.hintOrWarning.setText(R.string.error_publish_avatar_offline); } - } - } else { - this.avatarUri = this.defaultUri; - loadImageIntoPreview(this.defaultUri); - this.secondaryHint.setVisibility(View.INVISIBLE); - } - } else { - loadImageIntoPreview(avatarUri); - } - String account; - if (Config.DOMAIN_LOCK != null) { - account = this.account.getJid().getLocalpart(); - } else { - account = this.account.getJid().toBareJid().toString(); - } - this.accountTextView.setText(account); - } - } + } + } else { + this.avatarUri = this.defaultUri; + loadImageIntoPreview(this.defaultUri); + this.secondaryHint.setVisibility(View.INVISIBLE); + } + } else { + loadImageIntoPreview(avatarUri); + } + String account; + if (Config.DOMAIN_LOCK != null) { + account = this.account.getJid().getLocalpart(); + } else { + account = this.account.getJid().toBareJid().toString(); + } + this.accountTextView.setText(account); + } + } - @Override - protected void onStart() { - super.onStart(); - if (getIntent() != null) { - this.mInitialAccountSetup = getIntent().getBooleanExtra("setup", false); - } - if (this.mInitialAccountSetup) { - this.cancelButton.setText(R.string.skip); - } - } + @Override + protected void onStart() { + super.onStart(); + if (getIntent() != null) { + this.mInitialAccountSetup = getIntent().getBooleanExtra("setup", false); + } + if (this.mInitialAccountSetup) { + this.cancelButton.setText(R.string.skip); + } + } - protected void loadImageIntoPreview(Uri uri) { - Bitmap bm = null; - try { - bm = xmppConnectionService.getFileBackend().cropCenterSquare(uri, getPixel(Config.AVATAR_SIZE)); - } catch (Exception e) { - e.printStackTrace(); - } + protected void loadImageIntoPreview(Uri uri) { + Bitmap bm = null; + try { + bm = xmppConnectionService.getFileBackend().cropCenterSquare(uri, getPixel(Config.AVATAR_SIZE)); + } catch (Exception e) { + e.printStackTrace(); + } - if (bm == null) { - disablePublishButton(); - this.hintOrWarning.setTextColor(getWarningTextColor()); - this.hintOrWarning - .setText(R.string.error_publish_avatar_converting); - return; - } - this.avatar.setImageBitmap(bm); - if (support) { - enablePublishButton(); - this.publishButton.setText(R.string.publish); - this.hintOrWarning.setText(R.string.publish_avatar_explanation); - this.hintOrWarning.setTextColor(getPrimaryTextColor()); - } else { - disablePublishButton(); - this.hintOrWarning.setTextColor(getWarningTextColor()); + if (bm == null) { + disablePublishButton(); + this.hintOrWarning.setTextColor(getWarningTextColor()); + this.hintOrWarning + .setText(R.string.error_publish_avatar_converting); + return; + } + this.avatar.setImageBitmap(bm); + if (support) { + enablePublishButton(); + this.publishButton.setText(R.string.publish); + this.hintOrWarning.setText(R.string.publish_avatar_explanation); + this.hintOrWarning.setTextColor(getPrimaryTextColor()); + } else { + disablePublishButton(); + this.hintOrWarning.setTextColor(getWarningTextColor()); if (account.getStatus() == Account.State.ONLINE) { this.hintOrWarning.setText(R.string.error_publish_avatar_no_server_support); } else { this.hintOrWarning.setText(R.string.error_publish_avatar_offline); } - } - if (this.defaultUri != null && uri.equals(this.defaultUri)) { - this.secondaryHint.setVisibility(View.INVISIBLE); - this.avatar.setOnLongClickListener(null); - } else if (this.defaultUri != null) { - this.secondaryHint.setVisibility(View.VISIBLE); - this.avatar.setOnLongClickListener(this.backToDefaultListener); - } - } + } + if (this.defaultUri != null && uri.equals(this.defaultUri)) { + this.secondaryHint.setVisibility(View.INVISIBLE); + this.avatar.setOnLongClickListener(null); + } else if (this.defaultUri != null) { + this.secondaryHint.setVisibility(View.VISIBLE); + this.avatar.setOnLongClickListener(this.backToDefaultListener); + } + } - protected void enablePublishButton() { - this.publishButton.setEnabled(true); - this.publishButton.setTextColor(getPrimaryTextColor()); - } + protected void enablePublishButton() { + this.publishButton.setEnabled(true); + this.publishButton.setTextColor(getPrimaryTextColor()); + } - protected void disablePublishButton() { - this.publishButton.setEnabled(false); - this.publishButton.setTextColor(getSecondaryTextColor()); - } + protected void disablePublishButton() { + this.publishButton.setEnabled(false); + this.publishButton.setTextColor(getSecondaryTextColor()); + } - public void refreshUiReal() { - //nothing to do. This Activity doesn't implement any listeners - } + public void refreshUiReal() { + //nothing to do. This Activity doesn't implement any listeners + } } diff --git a/src/main/java/de/pixart/messenger/ui/RecordingActivity.java b/src/main/java/de/pixart/messenger/ui/RecordingActivity.java index ed753022b..e5e43f290 100644 --- a/src/main/java/de/pixart/messenger/ui/RecordingActivity.java +++ b/src/main/java/de/pixart/messenger/ui/RecordingActivity.java @@ -26,139 +26,139 @@ import de.pixart.messenger.persistance.FileBackend; public class RecordingActivity extends Activity implements View.OnClickListener { - private TextView mTimerTextView; - private Button mCancelButton; - private Button mStopButton; - - private MediaRecorder mRecorder; - private long mStartTime = 0; - - private int[] amplitudes = new int[100]; - private int i = 0; - - private Handler mHandler = new Handler(); - private Runnable mTickExecutor = new Runnable() { - @Override - public void run() { - tick(); - mHandler.postDelayed(mTickExecutor,100); - } - }; - private File mOutputFile; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_recording); - this.mTimerTextView = (TextView) this.findViewById(R.id.timer); - this.mCancelButton = (Button) this.findViewById(R.id.cancel_button); - this.mCancelButton.setOnClickListener(this); - this.mStopButton = (Button) this.findViewById(R.id.share_button); - this.mStopButton.setOnClickListener(this); + private TextView mTimerTextView; + private Button mCancelButton; + private Button mStopButton; + + private MediaRecorder mRecorder; + private long mStartTime = 0; + + private int[] amplitudes = new int[100]; + private int i = 0; + + private Handler mHandler = new Handler(); + private Runnable mTickExecutor = new Runnable() { + @Override + public void run() { + tick(); + mHandler.postDelayed(mTickExecutor, 100); + } + }; + private File mOutputFile; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_recording); + this.mTimerTextView = (TextView) this.findViewById(R.id.timer); + this.mCancelButton = (Button) this.findViewById(R.id.cancel_button); + this.mCancelButton.setOnClickListener(this); + this.mStopButton = (Button) this.findViewById(R.id.share_button); + this.mStopButton.setOnClickListener(this); this.setFinishOnTouchOutside(false); - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } - @Override - protected void onStart() { - super.onStart(); - Log.d(Config.LOGTAG, "output: " + getOutputFile()); + @Override + protected void onStart() { + super.onStart(); + Log.d(Config.LOGTAG, "output: " + getOutputFile()); if (!startRecording()) { mStopButton.setEnabled(false); mStopButton.setTextColor(0x8a000000); - Toast.makeText(this,R.string.unable_to_start_recording,Toast.LENGTH_SHORT).show(); + Toast.makeText(this, R.string.unable_to_start_recording, Toast.LENGTH_SHORT).show(); + } + } + + @Override + protected void onStop() { + super.onStop(); + if (mRecorder != null) { + stopRecording(false); + } + } + + private boolean startRecording() { + mRecorder = new MediaRecorder(); + mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); + mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); + mRecorder.setAudioEncodingBitRate(48000); + } else { + mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); + mRecorder.setAudioEncodingBitRate(48000); } - } - - @Override - protected void onStop() { - super.onStop(); - if (mRecorder != null) { - stopRecording(false); - } - } - - private boolean startRecording() { - mRecorder = new MediaRecorder(); - mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); - mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); - mRecorder.setAudioEncodingBitRate(48000); - } else { - mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); - mRecorder.setAudioEncodingBitRate(48000); - } - mRecorder.setAudioSamplingRate(48000); - mOutputFile = getOutputFile(); - mOutputFile.getParentFile().mkdirs(); - mRecorder.setOutputFile(mOutputFile.getAbsolutePath()); - - try { - mRecorder.prepare(); - mRecorder.start(); - mStartTime = SystemClock.elapsedRealtime(); - mHandler.postDelayed(mTickExecutor, 100); - Log.d(Config.LOGTAG,"started recording to "+mOutputFile.getAbsolutePath()); + mRecorder.setAudioSamplingRate(48000); + mOutputFile = getOutputFile(); + mOutputFile.getParentFile().mkdirs(); + mRecorder.setOutputFile(mOutputFile.getAbsolutePath()); + + try { + mRecorder.prepare(); + mRecorder.start(); + mStartTime = SystemClock.elapsedRealtime(); + mHandler.postDelayed(mTickExecutor, 100); + Log.d(Config.LOGTAG, "started recording to " + mOutputFile.getAbsolutePath()); return true; - } catch (Exception e) { - Log.e(Config.LOGTAG, "prepare() failed "+e.getMessage()); + } catch (Exception e) { + Log.e(Config.LOGTAG, "prepare() failed " + e.getMessage()); return false; - } - } - - protected void stopRecording(boolean saveFile) { - mRecorder.stop(); - mRecorder.release(); - mRecorder = null; - mStartTime = 0; - mHandler.removeCallbacks(mTickExecutor); - if (!saveFile && mOutputFile != null) { - mOutputFile.delete(); - } - } - - private File getOutputFile() { - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US); - return new File(FileBackend.getConversationsAudioDirectory() + "/" - + dateFormat.format(new Date()) - + ".m4a"); - } - - private void tick() { - long time = (mStartTime < 0) ? 0 : (SystemClock.elapsedRealtime() - mStartTime); - int minutes = (int) (time / 60000); - int seconds = (int) (time / 1000) % 60; - int milliseconds = (int) (time / 100) % 10; - mTimerTextView.setText(minutes+":"+(seconds < 10 ? "0"+seconds : seconds)+"."+milliseconds); - if (mRecorder != null) { - amplitudes[i] = mRecorder.getMaxAmplitude(); - //Log.d(Config.LOGTAG,"amplitude: "+(amplitudes[i] * 100 / 32767)); - if (i >= amplitudes.length -1) { - i = 0; - } else { - ++i; - } - } - } - - @Override - public void onClick(View view) { - switch (view.getId()) { - case R.id.cancel_button: - stopRecording(false); - setResult(RESULT_CANCELED); - finish(); - break; - case R.id.share_button: - stopRecording(true); - Uri uri = Uri.parse("file://"+mOutputFile.getAbsolutePath()); - Intent scanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); - scanIntent.setData(uri); - sendBroadcast(scanIntent); - setResult(Activity.RESULT_OK, new Intent().setData(uri)); - finish(); - break; - } - } + } + } + + protected void stopRecording(boolean saveFile) { + mRecorder.stop(); + mRecorder.release(); + mRecorder = null; + mStartTime = 0; + mHandler.removeCallbacks(mTickExecutor); + if (!saveFile && mOutputFile != null) { + mOutputFile.delete(); + } + } + + private File getOutputFile() { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US); + return new File(FileBackend.getConversationsAudioDirectory() + "/" + + dateFormat.format(new Date()) + + ".m4a"); + } + + private void tick() { + long time = (mStartTime < 0) ? 0 : (SystemClock.elapsedRealtime() - mStartTime); + int minutes = (int) (time / 60000); + int seconds = (int) (time / 1000) % 60; + int milliseconds = (int) (time / 100) % 10; + mTimerTextView.setText(minutes + ":" + (seconds < 10 ? "0" + seconds : seconds) + "." + milliseconds); + if (mRecorder != null) { + amplitudes[i] = mRecorder.getMaxAmplitude(); + //Log.d(Config.LOGTAG,"amplitude: "+(amplitudes[i] * 100 / 32767)); + if (i >= amplitudes.length - 1) { + i = 0; + } else { + ++i; + } + } + } + + @Override + public void onClick(View view) { + switch (view.getId()) { + case R.id.cancel_button: + stopRecording(false); + setResult(RESULT_CANCELED); + finish(); + break; + case R.id.share_button: + stopRecording(true); + Uri uri = Uri.parse("file://" + mOutputFile.getAbsolutePath()); + Intent scanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); + scanIntent.setData(uri); + sendBroadcast(scanIntent); + setResult(Activity.RESULT_OK, new Intent().setData(uri)); + finish(); + break; + } + } } \ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/SetPresenceActivity.java b/src/main/java/de/pixart/messenger/ui/SetPresenceActivity.java index 7214e560b..b4e88e5cc 100644 --- a/src/main/java/de/pixart/messenger/ui/SetPresenceActivity.java +++ b/src/main/java/de/pixart/messenger/ui/SetPresenceActivity.java @@ -29,204 +29,204 @@ import de.pixart.messenger.utils.UIHelper; public class SetPresenceActivity extends XmppActivity implements View.OnClickListener { - //data - protected Account mAccount; - private List mTemplates; - - //UI Elements - protected ScrollView mScrollView; - protected EditText mStatusMessage; - protected Spinner mShowSpinner; - protected CheckBox mAllAccounts; - protected LinearLayout mTemplatesView; - private Pair mPostponedActivityResult; - - private Runnable onPresenceChanged = new Runnable() { - @Override - public void run() { - finish(); - } - }; - - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_set_presence); - mScrollView = (ScrollView) findViewById(R.id.scroll_view); - mShowSpinner = (Spinner) findViewById(R.id.presence_show); - ArrayAdapter adapter = ArrayAdapter.createFromResource(this, - R.array.presence_show_options, - R.layout.simple_list_item); - mShowSpinner.setAdapter(adapter); - mShowSpinner.setSelection(1); - mStatusMessage = (EditText) findViewById(R.id.presence_status_message); - mAllAccounts = (CheckBox) findViewById(R.id.all_accounts); - mTemplatesView = (LinearLayout) findViewById(R.id.templates); - final Button changePresence = (Button) findViewById(R.id.change_presence); - changePresence.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - executeChangePresence(); - } - }); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.change_presence, menu); - return super.onCreateOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - if (item.getItemId() == R.id.action_account_details) { - if (mAccount != null) { - switchToAccount(mAccount); - } - return true; - } else { - return super.onOptionsItemSelected(item); - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (resultCode == RESULT_OK) { - if (xmppConnectionServiceBound && mAccount != null) { - if (requestCode == REQUEST_ANNOUNCE_PGP) { - announcePgp(mAccount, null, onPresenceChanged); - } - this.mPostponedActivityResult = null; - } else { - this.mPostponedActivityResult = new Pair<>(requestCode, data); - } - } - } - - private void executeChangePresence() { - Presence.Status status = getStatusFromSpinner(); - boolean allAccounts = mAllAccounts.isChecked(); - String statusMessage = mStatusMessage.getText().toString().trim(); - if (allAccounts && noAccountUsesPgp()) { - xmppConnectionService.changeStatus(status, statusMessage); - finish(); - } else if (mAccount != null) { - if (mAccount.getPgpId() == 0 || !hasPgp()) { - xmppConnectionService.changeStatus(mAccount, status, statusMessage, true); - finish(); - } else { - xmppConnectionService.changeStatus(mAccount, status, statusMessage, false); - announcePgp(mAccount, null, onPresenceChanged); - } - } - } - - private Presence.Status getStatusFromSpinner() { - switch (mShowSpinner.getSelectedItemPosition()) { - case 0: - return Presence.Status.CHAT; - case 2: - return Presence.Status.AWAY; - case 3: - return Presence.Status.XA; - case 4: - return Presence.Status.DND; - default: - return Presence.Status.ONLINE; - } - } - - private void setStatusInSpinner(Presence.Status status) { - switch(status) { - case AWAY: - mShowSpinner.setSelection(2); - break; - case XA: - mShowSpinner.setSelection(3); - break; - case CHAT: - mShowSpinner.setSelection(0); - break; - case DND: - mShowSpinner.setSelection(4); - break; - default: - mShowSpinner.setSelection(1); - break; - } - } - - @Override - protected void refreshUiReal() { - - } - - @Override - void onBackendConnected() { - mAccount = extractAccount(getIntent()); - if (mAccount != null) { - setStatusInSpinner(mAccount.getPresenceStatus()); - String message = mAccount.getPresenceStatusMessage(); - if (mStatusMessage.getText().length() == 0 && message != null) { - mStatusMessage.append(message); - } - mTemplates = xmppConnectionService.getPresenceTemplates(mAccount); - if (this.mPostponedActivityResult != null) { - this.onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second); - } - boolean e = noAccountUsesPgp(); - mAllAccounts.setEnabled(e); - mAllAccounts.setTextColor(e ? getPrimaryTextColor() : getSecondaryTextColor()); - } - redrawTemplates(); - } - - private void redrawTemplates() { - if (mTemplates == null || mTemplates.size() == 0) { - mTemplatesView.setVisibility(View.GONE); - } else { - mTemplatesView.removeAllViews(); - mTemplatesView.setVisibility(View.VISIBLE); - LayoutInflater inflater = getLayoutInflater(); - for (PresenceTemplate template : mTemplates) { - View templateLayout = inflater.inflate(R.layout.presence_template, mTemplatesView, false); - templateLayout.setTag(template); - setListItemBackgroundOnView(templateLayout); - templateLayout.setOnClickListener(this); - TextView message = (TextView) templateLayout.findViewById(R.id.presence_status_message); - TextView status = (TextView) templateLayout.findViewById(R.id.status); - ImageButton button = (ImageButton) templateLayout.findViewById(R.id.delete_button); - button.setTag(template); - button.setOnClickListener(this); - ListItem.Tag tag = UIHelper.getTagForStatus(this, template.getStatus()); - status.setText(tag.getName()); - status.setBackgroundColor(tag.getColor()); - message.setText(template.getStatusMessage()); - mTemplatesView.addView(templateLayout); - } - } - } - - @Override - public void onClick(View v) { - PresenceTemplate template = (PresenceTemplate) v.getTag(); - if (template == null) { - return; - } - if (v.getId() == R.id.presence_template) { - setStatusInSpinner(template.getStatus()); - mStatusMessage.getEditableText().clear(); - mStatusMessage.getEditableText().append(template.getStatusMessage()); - new Handler().post(new Runnable() { - @Override - public void run() { - mScrollView.smoothScrollTo(0,0); - } - }); - } else if (v.getId() == R.id.delete_button) { - xmppConnectionService.databaseBackend.deletePresenceTemplate(template); - mTemplates.remove(template); - redrawTemplates(); - } - } + //data + protected Account mAccount; + private List mTemplates; + + //UI Elements + protected ScrollView mScrollView; + protected EditText mStatusMessage; + protected Spinner mShowSpinner; + protected CheckBox mAllAccounts; + protected LinearLayout mTemplatesView; + private Pair mPostponedActivityResult; + + private Runnable onPresenceChanged = new Runnable() { + @Override + public void run() { + finish(); + } + }; + + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_set_presence); + mScrollView = (ScrollView) findViewById(R.id.scroll_view); + mShowSpinner = (Spinner) findViewById(R.id.presence_show); + ArrayAdapter adapter = ArrayAdapter.createFromResource(this, + R.array.presence_show_options, + R.layout.simple_list_item); + mShowSpinner.setAdapter(adapter); + mShowSpinner.setSelection(1); + mStatusMessage = (EditText) findViewById(R.id.presence_status_message); + mAllAccounts = (CheckBox) findViewById(R.id.all_accounts); + mTemplatesView = (LinearLayout) findViewById(R.id.templates); + final Button changePresence = (Button) findViewById(R.id.change_presence); + changePresence.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + executeChangePresence(); + } + }); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.change_presence, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + if (item.getItemId() == R.id.action_account_details) { + if (mAccount != null) { + switchToAccount(mAccount); + } + return true; + } else { + return super.onOptionsItemSelected(item); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode == RESULT_OK) { + if (xmppConnectionServiceBound && mAccount != null) { + if (requestCode == REQUEST_ANNOUNCE_PGP) { + announcePgp(mAccount, null, onPresenceChanged); + } + this.mPostponedActivityResult = null; + } else { + this.mPostponedActivityResult = new Pair<>(requestCode, data); + } + } + } + + private void executeChangePresence() { + Presence.Status status = getStatusFromSpinner(); + boolean allAccounts = mAllAccounts.isChecked(); + String statusMessage = mStatusMessage.getText().toString().trim(); + if (allAccounts && noAccountUsesPgp()) { + xmppConnectionService.changeStatus(status, statusMessage); + finish(); + } else if (mAccount != null) { + if (mAccount.getPgpId() == 0 || !hasPgp()) { + xmppConnectionService.changeStatus(mAccount, status, statusMessage, true); + finish(); + } else { + xmppConnectionService.changeStatus(mAccount, status, statusMessage, false); + announcePgp(mAccount, null, onPresenceChanged); + } + } + } + + private Presence.Status getStatusFromSpinner() { + switch (mShowSpinner.getSelectedItemPosition()) { + case 0: + return Presence.Status.CHAT; + case 2: + return Presence.Status.AWAY; + case 3: + return Presence.Status.XA; + case 4: + return Presence.Status.DND; + default: + return Presence.Status.ONLINE; + } + } + + private void setStatusInSpinner(Presence.Status status) { + switch (status) { + case AWAY: + mShowSpinner.setSelection(2); + break; + case XA: + mShowSpinner.setSelection(3); + break; + case CHAT: + mShowSpinner.setSelection(0); + break; + case DND: + mShowSpinner.setSelection(4); + break; + default: + mShowSpinner.setSelection(1); + break; + } + } + + @Override + protected void refreshUiReal() { + + } + + @Override + void onBackendConnected() { + mAccount = extractAccount(getIntent()); + if (mAccount != null) { + setStatusInSpinner(mAccount.getPresenceStatus()); + String message = mAccount.getPresenceStatusMessage(); + if (mStatusMessage.getText().length() == 0 && message != null) { + mStatusMessage.append(message); + } + mTemplates = xmppConnectionService.getPresenceTemplates(mAccount); + if (this.mPostponedActivityResult != null) { + this.onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second); + } + boolean e = noAccountUsesPgp(); + mAllAccounts.setEnabled(e); + mAllAccounts.setTextColor(e ? getPrimaryTextColor() : getSecondaryTextColor()); + } + redrawTemplates(); + } + + private void redrawTemplates() { + if (mTemplates == null || mTemplates.size() == 0) { + mTemplatesView.setVisibility(View.GONE); + } else { + mTemplatesView.removeAllViews(); + mTemplatesView.setVisibility(View.VISIBLE); + LayoutInflater inflater = getLayoutInflater(); + for (PresenceTemplate template : mTemplates) { + View templateLayout = inflater.inflate(R.layout.presence_template, mTemplatesView, false); + templateLayout.setTag(template); + setListItemBackgroundOnView(templateLayout); + templateLayout.setOnClickListener(this); + TextView message = (TextView) templateLayout.findViewById(R.id.presence_status_message); + TextView status = (TextView) templateLayout.findViewById(R.id.status); + ImageButton button = (ImageButton) templateLayout.findViewById(R.id.delete_button); + button.setTag(template); + button.setOnClickListener(this); + ListItem.Tag tag = UIHelper.getTagForStatus(this, template.getStatus()); + status.setText(tag.getName()); + status.setBackgroundColor(tag.getColor()); + message.setText(template.getStatusMessage()); + mTemplatesView.addView(templateLayout); + } + } + } + + @Override + public void onClick(View v) { + PresenceTemplate template = (PresenceTemplate) v.getTag(); + if (template == null) { + return; + } + if (v.getId() == R.id.presence_template) { + setStatusInSpinner(template.getStatus()); + mStatusMessage.getEditableText().clear(); + mStatusMessage.getEditableText().append(template.getStatusMessage()); + new Handler().post(new Runnable() { + @Override + public void run() { + mScrollView.smoothScrollTo(0, 0); + } + }); + } else if (v.getId() == R.id.delete_button) { + xmppConnectionService.databaseBackend.deletePresenceTemplate(template); + mTemplates.remove(template); + redrawTemplates(); + } + } } diff --git a/src/main/java/de/pixart/messenger/ui/SettingsActivity.java b/src/main/java/de/pixart/messenger/ui/SettingsActivity.java index 05b14d215..51c41c2ea 100644 --- a/src/main/java/de/pixart/messenger/ui/SettingsActivity.java +++ b/src/main/java/de/pixart/messenger/ui/SettingsActivity.java @@ -33,269 +33,269 @@ import de.pixart.messenger.xmpp.jid.InvalidJidException; import de.pixart.messenger.xmpp.jid.Jid; public class SettingsActivity extends XmppActivity implements - OnSharedPreferenceChangeListener { + OnSharedPreferenceChangeListener { - public static final int REQUEST_WRITE_LOGS = 0xbf8701; - private SettingsFragment mSettingsFragment; + public static final int REQUEST_WRITE_LOGS = 0xbf8701; + private SettingsFragment mSettingsFragment; - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - FragmentManager fm = getFragmentManager(); - mSettingsFragment = (SettingsFragment) fm.findFragmentById(android.R.id.content); - if (mSettingsFragment == null || !mSettingsFragment.getClass().equals(SettingsFragment.class)) { - mSettingsFragment = new SettingsFragment(); - fm.beginTransaction().replace(android.R.id.content, mSettingsFragment).commit(); - } - } + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + FragmentManager fm = getFragmentManager(); + mSettingsFragment = (SettingsFragment) fm.findFragmentById(android.R.id.content); + if (mSettingsFragment == null || !mSettingsFragment.getClass().equals(SettingsFragment.class)) { + mSettingsFragment = new SettingsFragment(); + fm.beginTransaction().replace(android.R.id.content, mSettingsFragment).commit(); + } + } - @Override - void onBackendConnected() { + @Override + void onBackendConnected() { - } + } - @Override - public void onStart() { - super.onStart(); - PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this); - ListPreference resources = (ListPreference) mSettingsFragment.findPreference("resource"); - if (resources != null) { - ArrayList entries = new ArrayList<>(Arrays.asList(resources.getEntries())); - if (!entries.contains(Build.MODEL)) { - entries.add(0, Build.MODEL); - resources.setEntries(entries.toArray(new CharSequence[entries.size()])); - resources.setEntryValues(entries.toArray(new CharSequence[entries.size()])); - } - } + @Override + public void onStart() { + super.onStart(); + PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this); + ListPreference resources = (ListPreference) mSettingsFragment.findPreference("resource"); + if (resources != null) { + ArrayList entries = new ArrayList<>(Arrays.asList(resources.getEntries())); + if (!entries.contains(Build.MODEL)) { + entries.add(0, Build.MODEL); + resources.setEntries(entries.toArray(new CharSequence[entries.size()])); + resources.setEntryValues(entries.toArray(new CharSequence[entries.size()])); + } + } - if (Config.FORCE_ORBOT) { - PreferenceCategory connectionOptions = (PreferenceCategory) mSettingsFragment.findPreference("connection_options"); - PreferenceScreen expert = (PreferenceScreen) mSettingsFragment.findPreference("expert"); - if (connectionOptions != null) { - expert.removePreference(connectionOptions); - } - } + if (Config.FORCE_ORBOT) { + PreferenceCategory connectionOptions = (PreferenceCategory) mSettingsFragment.findPreference("connection_options"); + PreferenceScreen expert = (PreferenceScreen) mSettingsFragment.findPreference("expert"); + if (connectionOptions != null) { + expert.removePreference(connectionOptions); + } + } - final Preference removeCertsPreference = mSettingsFragment.findPreference("remove_trusted_certificates"); - removeCertsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - final MemorizingTrustManager mtm = xmppConnectionService.getMemorizingTrustManager(); - final ArrayList aliases = Collections.list(mtm.getCertificates()); - if (aliases.size() == 0) { - displayToast(getString(R.string.toast_no_trusted_certs)); - return true; - } + final Preference removeCertsPreference = mSettingsFragment.findPreference("remove_trusted_certificates"); + removeCertsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + final MemorizingTrustManager mtm = xmppConnectionService.getMemorizingTrustManager(); + final ArrayList aliases = Collections.list(mtm.getCertificates()); + if (aliases.size() == 0) { + displayToast(getString(R.string.toast_no_trusted_certs)); + return true; + } final ArrayList selectedItems = new ArrayList<>(); - final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(SettingsActivity.this); - dialogBuilder.setTitle(getResources().getString(R.string.dialog_manage_certs_title)); - dialogBuilder.setMultiChoiceItems(aliases.toArray(new CharSequence[aliases.size()]), null, - new DialogInterface.OnMultiChoiceClickListener() { - @Override - public void onClick(DialogInterface dialog, int indexSelected, - boolean isChecked) { - if (isChecked) { - selectedItems.add(indexSelected); - } else if (selectedItems.contains(indexSelected)) { - selectedItems.remove(Integer.valueOf(indexSelected)); - } - if (selectedItems.size() > 0) - ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true); - else { - ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false); - } - } - }); + final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(SettingsActivity.this); + dialogBuilder.setTitle(getResources().getString(R.string.dialog_manage_certs_title)); + dialogBuilder.setMultiChoiceItems(aliases.toArray(new CharSequence[aliases.size()]), null, + new DialogInterface.OnMultiChoiceClickListener() { + @Override + public void onClick(DialogInterface dialog, int indexSelected, + boolean isChecked) { + if (isChecked) { + selectedItems.add(indexSelected); + } else if (selectedItems.contains(indexSelected)) { + selectedItems.remove(Integer.valueOf(indexSelected)); + } + if (selectedItems.size() > 0) + ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true); + else { + ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false); + } + } + }); - dialogBuilder.setPositiveButton( - getResources().getString(R.string.dialog_manage_certs_positivebutton), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - int count = selectedItems.size(); - if (count > 0) { - for (int i = 0; i < count; i++) { - try { - Integer item = Integer.valueOf(selectedItems.get(i).toString()); - String alias = aliases.get(item); - mtm.deleteCertificate(alias); - } catch (KeyStoreException e) { - e.printStackTrace(); - displayToast("Error: " + e.getLocalizedMessage()); - } - } - if (xmppConnectionServiceBound) { - reconnectAccounts(); - } - displayToast(getResources().getQuantityString(R.plurals.toast_delete_certificates, count, count)); - } - } - }); - dialogBuilder.setNegativeButton(getResources().getString(R.string.dialog_manage_certs_negativebutton), null); - AlertDialog removeCertsDialog = dialogBuilder.create(); - removeCertsDialog.show(); - removeCertsDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); - return true; - } - }); + dialogBuilder.setPositiveButton( + getResources().getString(R.string.dialog_manage_certs_positivebutton), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + int count = selectedItems.size(); + if (count > 0) { + for (int i = 0; i < count; i++) { + try { + Integer item = Integer.valueOf(selectedItems.get(i).toString()); + String alias = aliases.get(item); + mtm.deleteCertificate(alias); + } catch (KeyStoreException e) { + e.printStackTrace(); + displayToast("Error: " + e.getLocalizedMessage()); + } + } + if (xmppConnectionServiceBound) { + reconnectAccounts(); + } + displayToast(getResources().getQuantityString(R.plurals.toast_delete_certificates, count, count)); + } + } + }); + dialogBuilder.setNegativeButton(getResources().getString(R.string.dialog_manage_certs_negativebutton), null); + AlertDialog removeCertsDialog = dialogBuilder.create(); + removeCertsDialog.show(); + removeCertsDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); + return true; + } + }); - final Preference exportLogsPreference = mSettingsFragment.findPreference("export_logs"); - exportLogsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { + final Preference exportLogsPreference = mSettingsFragment.findPreference("export_logs"); + exportLogsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { if (hasStoragePermission(REQUEST_WRITE_LOGS)) { startExport(); } - return true; - } - }); + return true; + } + }); final Preference deleteOmemoPreference = mSettingsFragment.findPreference("delete_omemo_identities"); - deleteOmemoPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - deleteOmemoIdentities(); - return true; - } - }); - } + deleteOmemoPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + deleteOmemoIdentities(); + return true; + } + }); + } - private void deleteOmemoIdentities() { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.pref_delete_omemo_identities); - final List accounts = new ArrayList<>(); - for(Account account : xmppConnectionService.getAccounts()) { - if (!account.isOptionSet(Account.OPTION_DISABLED)) { - accounts.add(account.getJid().toBareJid().toString()); - } - } - final boolean[] checkedItems = new boolean[accounts.size()]; - builder.setMultiChoiceItems(accounts.toArray(new CharSequence[accounts.size()]), checkedItems, new DialogInterface.OnMultiChoiceClickListener() { - @Override - public void onClick(DialogInterface dialog, int which, boolean isChecked) { - checkedItems[which] = isChecked; - final AlertDialog alertDialog = (AlertDialog) dialog; - for(boolean item : checkedItems) { - if (item) { - alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true); - return; - } - } - alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false); - } - }); - builder.setNegativeButton(R.string.cancel,null); - builder.setPositiveButton(R.string.delete_selected_keys, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - for(int i = 0; i < checkedItems.length; ++i) { - if (checkedItems[i]) { - try { - Jid jid = Jid.fromString(accounts.get(i).toString()); - Account account = xmppConnectionService.findAccountByJid(jid); - if (account != null) { - account.getAxolotlService().regenerateKeys(true); - } - } catch (InvalidJidException e) { - // - } + private void deleteOmemoIdentities() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.pref_delete_omemo_identities); + final List accounts = new ArrayList<>(); + for (Account account : xmppConnectionService.getAccounts()) { + if (!account.isOptionSet(Account.OPTION_DISABLED)) { + accounts.add(account.getJid().toBareJid().toString()); + } + } + final boolean[] checkedItems = new boolean[accounts.size()]; + builder.setMultiChoiceItems(accounts.toArray(new CharSequence[accounts.size()]), checkedItems, new DialogInterface.OnMultiChoiceClickListener() { + @Override + public void onClick(DialogInterface dialog, int which, boolean isChecked) { + checkedItems[which] = isChecked; + final AlertDialog alertDialog = (AlertDialog) dialog; + for (boolean item : checkedItems) { + if (item) { + alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true); + return; + } + } + alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false); + } + }); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(R.string.delete_selected_keys, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + for (int i = 0; i < checkedItems.length; ++i) { + if (checkedItems[i]) { + try { + Jid jid = Jid.fromString(accounts.get(i).toString()); + Account account = xmppConnectionService.findAccountByJid(jid); + if (account != null) { + account.getAxolotlService().regenerateKeys(true); + } + } catch (InvalidJidException e) { + // + } - } - } - } - }); - AlertDialog dialog = builder.create(); - dialog.show(); - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); - } + } + } + } + }); + AlertDialog dialog = builder.create(); + dialog.show(); + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); + } - @Override - public void onStop() { - super.onStop(); - PreferenceManager.getDefaultSharedPreferences(this) - .unregisterOnSharedPreferenceChangeListener(this); - } + @Override + public void onStop() { + super.onStop(); + PreferenceManager.getDefaultSharedPreferences(this) + .unregisterOnSharedPreferenceChangeListener(this); + } - @Override - public void onSharedPreferenceChanged(SharedPreferences preferences, String name) { - final List resendPresence = Arrays.asList( - "confirm_messages", - "xa_on_silent_mode", - "away_when_screen_off", - "allow_message_correction", - "treat_vibrate_as_silent", - "manually_change_presence", - "last_activity"); - if (name.equals("resource")) { - String resource = preferences.getString("resource", "mobile") - .toLowerCase(Locale.US); - if (xmppConnectionServiceBound) { - for (Account account : xmppConnectionService.getAccounts()) { - if (account.setResource(resource)) { - if (!account.isOptionSet(Account.OPTION_DISABLED)) { - XmppConnection connection = account.getXmppConnection(); - if (connection != null) { - connection.resetStreamId(); - } - xmppConnectionService.reconnectAccountInBackground(account); - } - } - } - } - } else if (resendPresence.contains(name)) { - if (xmppConnectionServiceBound) { - if (name.equals("away_when_screen_off") - || name.equals("manually_change_presence")) { - xmppConnectionService.toggleScreenEventReceiver(); - } - if (name.equals("manually_change_presence") && !noAccountUsesPgp()) { - Toast.makeText(this, R.string.republish_pgp_keys, Toast.LENGTH_LONG).show(); - } - xmppConnectionService.refreshAllPresences(); - } - } else if (name.equals("dont_trust_system_cas")) { - xmppConnectionService.updateMemorizingTrustmanager(); - reconnectAccounts(); - } else if (name.equals("use_tor")) { - reconnectAccounts(); - } + @Override + public void onSharedPreferenceChanged(SharedPreferences preferences, String name) { + final List resendPresence = Arrays.asList( + "confirm_messages", + "xa_on_silent_mode", + "away_when_screen_off", + "allow_message_correction", + "treat_vibrate_as_silent", + "manually_change_presence", + "last_activity"); + if (name.equals("resource")) { + String resource = preferences.getString("resource", "mobile") + .toLowerCase(Locale.US); + if (xmppConnectionServiceBound) { + for (Account account : xmppConnectionService.getAccounts()) { + if (account.setResource(resource)) { + if (!account.isOptionSet(Account.OPTION_DISABLED)) { + XmppConnection connection = account.getXmppConnection(); + if (connection != null) { + connection.resetStreamId(); + } + xmppConnectionService.reconnectAccountInBackground(account); + } + } + } + } + } else if (resendPresence.contains(name)) { + if (xmppConnectionServiceBound) { + if (name.equals("away_when_screen_off") + || name.equals("manually_change_presence")) { + xmppConnectionService.toggleScreenEventReceiver(); + } + if (name.equals("manually_change_presence") && !noAccountUsesPgp()) { + Toast.makeText(this, R.string.republish_pgp_keys, Toast.LENGTH_LONG).show(); + } + xmppConnectionService.refreshAllPresences(); + } + } else if (name.equals("dont_trust_system_cas")) { + xmppConnectionService.updateMemorizingTrustmanager(); + reconnectAccounts(); + } else if (name.equals("use_tor")) { + reconnectAccounts(); + } - } + } - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - if (grantResults.length > 0) - if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { - if (requestCode == REQUEST_WRITE_LOGS) { + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + if (grantResults.length > 0) + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + if (requestCode == REQUEST_WRITE_LOGS) { startExport(); - } - } else { - Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show(); - } - } + } + } else { + Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show(); + } + } private void startExport() { startService(new Intent(getApplicationContext(), ExportLogsService.class)); } private void displayToast(final String msg) { - runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(SettingsActivity.this, msg, Toast.LENGTH_LONG).show(); - } - }); - } + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(SettingsActivity.this, msg, Toast.LENGTH_LONG).show(); + } + }); + } - private void reconnectAccounts() { - for (Account account : xmppConnectionService.getAccounts()) { - if (!account.isOptionSet(Account.OPTION_DISABLED)) { - xmppConnectionService.reconnectAccountInBackground(account); - } - } - } + private void reconnectAccounts() { + for (Account account : xmppConnectionService.getAccounts()) { + if (!account.isOptionSet(Account.OPTION_DISABLED)) { + xmppConnectionService.reconnectAccountInBackground(account); + } + } + } - public void refreshUiReal() { - //nothing to do. This Activity doesn't implement any listeners - } + public void refreshUiReal() { + //nothing to do. This Activity doesn't implement any listeners + } } diff --git a/src/main/java/de/pixart/messenger/ui/SettingsFragment.java b/src/main/java/de/pixart/messenger/ui/SettingsFragment.java index c9e6dc51a..119e79fb7 100644 --- a/src/main/java/de/pixart/messenger/ui/SettingsFragment.java +++ b/src/main/java/de/pixart/messenger/ui/SettingsFragment.java @@ -15,51 +15,51 @@ import de.pixart.messenger.R; public class SettingsFragment extends PreferenceFragment { - //http://stackoverflow.com/questions/16374820/action-bar-home-button-not-functional-with-nested-preferencescreen/16800527#16800527 - private void initializeActionBar(PreferenceScreen preferenceScreen) { - final Dialog dialog = preferenceScreen.getDialog(); + //http://stackoverflow.com/questions/16374820/action-bar-home-button-not-functional-with-nested-preferencescreen/16800527#16800527 + private void initializeActionBar(PreferenceScreen preferenceScreen) { + final Dialog dialog = preferenceScreen.getDialog(); - if (dialog != null) { - View homeBtn = dialog.findViewById(android.R.id.home); + if (dialog != null) { + View homeBtn = dialog.findViewById(android.R.id.home); - if (homeBtn != null) { - View.OnClickListener dismissDialogClickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - dialog.dismiss(); - } - }; + if (homeBtn != null) { + View.OnClickListener dismissDialogClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + dialog.dismiss(); + } + }; - ViewParent homeBtnContainer = homeBtn.getParent(); + ViewParent homeBtnContainer = homeBtn.getParent(); - if (homeBtnContainer instanceof FrameLayout) { - ViewGroup containerParent = (ViewGroup) homeBtnContainer.getParent(); - if (containerParent instanceof LinearLayout) { - ((LinearLayout) containerParent).setOnClickListener(dismissDialogClickListener); - } else { - ((FrameLayout) homeBtnContainer).setOnClickListener(dismissDialogClickListener); - } - } else { - homeBtn.setOnClickListener(dismissDialogClickListener); - } - } - } - } + if (homeBtnContainer instanceof FrameLayout) { + ViewGroup containerParent = (ViewGroup) homeBtnContainer.getParent(); + if (containerParent instanceof LinearLayout) { + ((LinearLayout) containerParent).setOnClickListener(dismissDialogClickListener); + } else { + ((FrameLayout) homeBtnContainer).setOnClickListener(dismissDialogClickListener); + } + } else { + homeBtn.setOnClickListener(dismissDialogClickListener); + } + } + } + } - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); - // Load the preferences from an XML resource - addPreferencesFromResource(R.xml.preferences); - } + // Load the preferences from an XML resource + addPreferencesFromResource(R.xml.preferences); + } - @Override - public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { - super.onPreferenceTreeClick(preferenceScreen, preference); - if (preference instanceof PreferenceScreen) { - initializeActionBar((PreferenceScreen) preference); - } - return false; - } + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { + super.onPreferenceTreeClick(preferenceScreen, preference); + if (preference instanceof PreferenceScreen) { + initializeActionBar((PreferenceScreen) preference); + } + return false; + } } diff --git a/src/main/java/de/pixart/messenger/ui/ShareLocationActivity.java b/src/main/java/de/pixart/messenger/ui/ShareLocationActivity.java index 2bb9dfd9d..e742b6590 100644 --- a/src/main/java/de/pixart/messenger/ui/ShareLocationActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ShareLocationActivity.java @@ -160,62 +160,62 @@ public class ShareLocationActivity extends Activity implements OnMapReadyCallbac } - @Override - public void onConnectionSuspended(int i) { + @Override + public void onConnectionSuspended(int i) { - } + } - @Override - public void onConnectionFailed(ConnectionResult connectionResult) { + @Override + public void onConnectionFailed(ConnectionResult connectionResult) { - } + } - @Override - public void onLocationChanged(Location location) { + @Override + public void onLocationChanged(Location location) { double longitude = location.getLongitude(); double latitude = location.getLatitude(); - if (this.mLastLocation == null) { - centerOnLocation(new LatLng(location.getLatitude(), location.getLongitude())); - this.mShareButton.setEnabled(true); - this.mShareButton.setTextColor(0xde000000); - this.mShareButton.setText(R.string.share); - } - this.mLastLocation = location; + if (this.mLastLocation == null) { + centerOnLocation(new LatLng(location.getLatitude(), location.getLongitude())); + this.mShareButton.setEnabled(true); + this.mShareButton.setTextColor(0xde000000); + this.mShareButton.setText(R.string.share); + } + this.mLastLocation = location; if (latitude != 0 && longitude != 0) { Double[] lat_long = new Double[]{latitude, longitude}; new ReverseGeocodingTask(getBaseContext()).execute(lat_long); } - } - - @TargetApi(Build.VERSION_CODES.KITKAT) - private boolean isLocationEnabledKitkat() { - try { - int locationMode = Settings.Secure.getInt(getContentResolver(), Settings.Secure.LOCATION_MODE); - return locationMode != Settings.Secure.LOCATION_MODE_OFF; - } catch (Settings.SettingNotFoundException e) { - return false; - } - } - - @SuppressWarnings("deprecation") - private boolean isLocationEnabledLegacy() { - String locationProviders = Settings.Secure.getString(getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED); - return !TextUtils.isEmpty(locationProviders); - } - - private boolean isLocationEnabled() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){ - return isLocationEnabledKitkat(); - }else{ - return isLocationEnabledLegacy(); - } - } + } + + @TargetApi(Build.VERSION_CODES.KITKAT) + private boolean isLocationEnabledKitkat() { + try { + int locationMode = Settings.Secure.getInt(getContentResolver(), Settings.Secure.LOCATION_MODE); + return locationMode != Settings.Secure.LOCATION_MODE_OFF; + } catch (Settings.SettingNotFoundException e) { + return false; + } + } + + @SuppressWarnings("deprecation") + private boolean isLocationEnabledLegacy() { + String locationProviders = Settings.Secure.getString(getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED); + return !TextUtils.isEmpty(locationProviders); + } + + private boolean isLocationEnabled() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + return isLocationEnabledKitkat(); + } else { + return isLocationEnabledLegacy(); + } + } private class ReverseGeocodingTask extends AsyncTask { Context mContext; - public ReverseGeocodingTask(Context context){ + public ReverseGeocodingTask(Context context) { super(); mContext = context; } @@ -228,10 +228,10 @@ public class ShareLocationActivity extends Activity implements OnMapReadyCallbac double longitude = params[1]; List

addresses = null; - String address=""; + String address = ""; try { - addresses = geocoder.getFromLocation(latitude, longitude,1); + addresses = geocoder.getFromLocation(latitude, longitude, 1); } catch (IOException e) { e.printStackTrace(); } @@ -244,7 +244,7 @@ public class ShareLocationActivity extends Activity implements OnMapReadyCallbac strAddress.append(Address.getAddressLine(i)).append("\n"); } address = strAddress.toString(); - address = address.substring(0, address.length()-1); //trim last \n + address = address.substring(0, address.length() - 1); //trim last \n } return address; @@ -256,7 +256,7 @@ public class ShareLocationActivity extends Activity implements OnMapReadyCallbac // Setting address of the touched Position mLocationInfo.setVisibility(View.VISIBLE); mSnackbarLocation.setText(address); - Log.d(Config.LOGTAG,"Location: Address = "+ address); + Log.d(Config.LOGTAG, "Location: Address = " + address); } } } diff --git a/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java b/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java index d9b0695ef..5df34457b 100644 --- a/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java @@ -35,214 +35,214 @@ import static java.lang.String.format; public class ShareWithActivity extends XmppActivity implements XmppConnectionService.OnConversationUpdate { - private boolean mReturnToPrevious = false; + private boolean mReturnToPrevious = false; private static final String STATE_SHARING_IS_RUNNING = "state_sharing_is_running"; static boolean ContactChosen = false; static boolean IntentReceived = false; boolean SharingIsRunning = false; - @Override - public void onConversationUpdate() { - refreshUi(); - } + @Override + public void onConversationUpdate() { + refreshUi(); + } - private class Share { - public List uris = new ArrayList<>(); - public boolean image; + private class Share { + public List uris = new ArrayList<>(); + public boolean image; public boolean video; - public String account; - public String contact; - public String text; - public String uuid; - public boolean multiple = false; - } - - private Share share; - - private static final int REQUEST_START_NEW_CONVERSATION = 0x0501; - private ListView mListView; - private ConversationAdapter mAdapter; - private List mConversations = new ArrayList<>(); - private Toast mToast; - private AtomicInteger attachmentCounter = new AtomicInteger(0); - - private UiCallback attachFileCallback = new UiCallback() { - - @Override - public void userInputRequried(PendingIntent pi, Message object) { - // TODO Auto-generated method stub - - } - - @Override - public void success(final Message message) { - xmppConnectionService.sendMessage(message); - runOnUiThread(new Runnable() { - @Override - public void run() { - if (attachmentCounter.decrementAndGet() <=0 ) { - int resId; - if (share.image && share.multiple) { - resId = R.string.shared_images_with_x; - } else if (share.image) { - resId = R.string.shared_image_with_x; + public String account; + public String contact; + public String text; + public String uuid; + public boolean multiple = false; + } + + private Share share; + + private static final int REQUEST_START_NEW_CONVERSATION = 0x0501; + private ListView mListView; + private ConversationAdapter mAdapter; + private List mConversations = new ArrayList<>(); + private Toast mToast; + private AtomicInteger attachmentCounter = new AtomicInteger(0); + + private UiCallback attachFileCallback = new UiCallback() { + + @Override + public void userInputRequried(PendingIntent pi, Message object) { + // TODO Auto-generated method stub + + } + + @Override + public void success(final Message message) { + xmppConnectionService.sendMessage(message); + runOnUiThread(new Runnable() { + @Override + public void run() { + if (attachmentCounter.decrementAndGet() <= 0) { + int resId; + if (share.image && share.multiple) { + resId = R.string.shared_images_with_x; + } else if (share.image) { + resId = R.string.shared_image_with_x; } else if (share.video) { resId = R.string.shared_video_with_x; - } else { - resId = R.string.shared_file_with_x; - } - replaceToast(getString(resId, message.getConversation().getName())); - if (mReturnToPrevious) { - finish(); - } else { - switchToConversation(message.getConversation()); - } - } - } - }); - } - - @Override - public void error(final int errorCode, Message object) { - runOnUiThread(new Runnable() { - @Override - public void run() { - replaceToast(getString(errorCode)); - if (attachmentCounter.decrementAndGet() <=0 ) { - finish(); - } - } - }); - } - }; - - protected void hideToast() { - if (mToast != null) { - mToast.cancel(); - } - } - - protected void replaceToast(String msg) { - hideToast(); - mToast = Toast.makeText(this, msg ,Toast.LENGTH_LONG); - mToast.show(); - } - - protected void onActivityResult(int requestCode, int resultCode, final Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (requestCode == REQUEST_START_NEW_CONVERSATION - && resultCode == RESULT_OK) { - share.contact = data.getStringExtra("contact"); - share.account = data.getStringExtra(EXTRA_ACCOUNT); - } - if (xmppConnectionServiceBound - && share != null - && share.contact != null - && share.account != null) { - share(); - } - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - if (getActionBar() != null) { - getActionBar().setDisplayHomeAsUpEnabled(false); - getActionBar().setHomeButtonEnabled(false); - } - - setContentView(R.layout.share_with); - setTitle(getString(R.string.title_activity_sharewith)); - - mListView = (ListView) findViewById(R.id.choose_conversation_list); - mAdapter = new ConversationAdapter(this, this.mConversations); - mListView.setAdapter(mAdapter); - mListView.setOnItemClickListener(new OnItemClickListener() { - - @Override - public void onItemClick(AdapterView arg0, View arg1, int position, long arg3) { - share(mConversations.get(position)); - } - }); + } else { + resId = R.string.shared_file_with_x; + } + replaceToast(getString(resId, message.getConversation().getName())); + if (mReturnToPrevious) { + finish(); + } else { + switchToConversation(message.getConversation()); + } + } + } + }); + } + + @Override + public void error(final int errorCode, Message object) { + runOnUiThread(new Runnable() { + @Override + public void run() { + replaceToast(getString(errorCode)); + if (attachmentCounter.decrementAndGet() <= 0) { + finish(); + } + } + }); + } + }; + + protected void hideToast() { + if (mToast != null) { + mToast.cancel(); + } + } + + protected void replaceToast(String msg) { + hideToast(); + mToast = Toast.makeText(this, msg, Toast.LENGTH_LONG); + mToast.show(); + } + + protected void onActivityResult(int requestCode, int resultCode, final Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_START_NEW_CONVERSATION + && resultCode == RESULT_OK) { + share.contact = data.getStringExtra("contact"); + share.account = data.getStringExtra(EXTRA_ACCOUNT); + } + if (xmppConnectionServiceBound + && share != null + && share.contact != null + && share.account != null) { + share(); + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (getActionBar() != null) { + getActionBar().setDisplayHomeAsUpEnabled(false); + getActionBar().setHomeButtonEnabled(false); + } + + setContentView(R.layout.share_with); + setTitle(getString(R.string.title_activity_sharewith)); + + mListView = (ListView) findViewById(R.id.choose_conversation_list); + mAdapter = new ConversationAdapter(this, this.mConversations); + mListView.setAdapter(mAdapter); + mListView.setOnItemClickListener(new OnItemClickListener() { + + @Override + public void onItemClick(AdapterView arg0, View arg1, int position, long arg3) { + share(mConversations.get(position)); + } + }); if (savedInstanceState != null) { SharingIsRunning = savedInstanceState.getBoolean(STATE_SHARING_IS_RUNNING, false); } - if (!SharingIsRunning) { + if (!SharingIsRunning) { Log.d(Config.LOGTAG, "ShareWithActivity onCreate: state restored"); this.share = new Share(); } else { Log.d(Config.LOGTAG, "ShareWithActivity onCreate: shring running, finish()"); this.finish(); } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.share_with, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case R.id.action_add: - final Intent intent = new Intent(getApplicationContext(), ChooseContactActivity.class); - startActivityForResult(intent, REQUEST_START_NEW_CONVERSATION); - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - public void onStart() { - super.onStart(); - Intent intent = getIntent(); - if (intent == null) { - return; - } else { + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.share_with, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + switch (item.getItemId()) { + case R.id.action_add: + final Intent intent = new Intent(getApplicationContext(), ChooseContactActivity.class); + startActivityForResult(intent, REQUEST_START_NEW_CONVERSATION); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onStart() { + super.onStart(); + Intent intent = getIntent(); + if (intent == null) { + return; + } else { IntentReceived = true; } Log.d(Config.LOGTAG, "ShareWithActivity onStart() getIntent " + intent.toString()); - this.mReturnToPrevious = getPreferences().getBoolean("return_to_previous", false); - final String type = intent.getType(); - final String action = intent.getAction(); - Log.d(Config.LOGTAG, "action: "+action+ ", type:"+type); - share.uuid = intent.getStringExtra("uuid"); - if (Intent.ACTION_SEND.equals(action)) { + this.mReturnToPrevious = getPreferences().getBoolean("return_to_previous", false); + final String type = intent.getType(); + final String action = intent.getAction(); + Log.d(Config.LOGTAG, "action: " + action + ", type:" + type); + share.uuid = intent.getStringExtra("uuid"); + if (Intent.ACTION_SEND.equals(action)) { final String subject = intent.getStringExtra(Intent.EXTRA_SUBJECT); - final String text = intent.getStringExtra(Intent.EXTRA_TEXT); - final Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM); + final String text = intent.getStringExtra(Intent.EXTRA_TEXT); + final Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM); Log.d(Config.LOGTAG, "ShareWithActivity onStart() Uri: " + uri); - if (type != null && uri != null && (text == null || !type.equals("text/plain"))) { - this.share.uris.clear(); - this.share.uris.add(uri); - this.share.image = type.startsWith("image/") || isImage(uri); + if (type != null && uri != null && (text == null || !type.equals("text/plain"))) { + this.share.uris.clear(); + this.share.uris.add(uri); + this.share.image = type.startsWith("image/") || isImage(uri); this.share.video = type.startsWith("video/") || isVideo(uri); - } else { + } else { if (subject != null) { this.share.text = format("[%s]%n%s", subject, text); } else { this.share.text = text; } - } - } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) { - this.share.image = type != null && type.startsWith("image/"); - if (!this.share.image) { - return; - } - this.share.uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); - } - if (xmppConnectionServiceBound) { - if (share.uuid != null) { - share(); - } else { - xmppConnectionService.populateWithOrderedConversations(mConversations, this.share.uris.size() == 0); - } - } - - } + } + } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) { + this.share.image = type != null && type.startsWith("image/"); + if (!this.share.image) { + return; + } + this.share.uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); + } + if (xmppConnectionServiceBound) { + if (share.uuid != null) { + share(); + } else { + xmppConnectionService.populateWithOrderedConversations(mConversations, this.share.uris.size() == 0); + } + } + + } @Override public void onSaveInstanceState(final Bundle savedInstanceState) { @@ -256,14 +256,14 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer super.onSaveInstanceState(savedInstanceState); } - protected boolean isImage(Uri uri) { - try { - String guess = URLConnection.guessContentTypeFromName(uri.toString()); - return (guess != null && guess.startsWith("image/")); - } catch (final StringIndexOutOfBoundsException ignored) { - return false; - } - } + protected boolean isImage(Uri uri) { + try { + String guess = URLConnection.guessContentTypeFromName(uri.toString()); + return (guess != null && guess.startsWith("image/")); + } catch (final StringIndexOutOfBoundsException ignored) { + return false; + } + } protected boolean isVideo(Uri uri) { try { @@ -274,65 +274,65 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer } } - @Override - void onBackendConnected() { - if (xmppConnectionServiceBound && share != null - && ((share.contact != null && share.account != null) || share.uuid != null)) { - share(); - return; - } - refreshUiReal(); - } - - private void share() { - final Conversation conversation; - if (share.uuid != null) { - conversation = xmppConnectionService.findConversationByUuid(share.uuid); - if (conversation == null) { - return; - } - }else{ - Account account; - try { - account = xmppConnectionService.findAccountByJid(Jid.fromString(share.account)); - } catch (final InvalidJidException e) { - account = null; - } - if (account == null) { - return; - } - - try { - conversation = xmppConnectionService - .findOrCreateConversation(account, Jid.fromString(share.contact), false); - } catch (final InvalidJidException e) { - return; - } - } + @Override + void onBackendConnected() { + if (xmppConnectionServiceBound && share != null + && ((share.contact != null && share.account != null) || share.uuid != null)) { + share(); + return; + } + refreshUiReal(); + } + + private void share() { + final Conversation conversation; + if (share.uuid != null) { + conversation = xmppConnectionService.findConversationByUuid(share.uuid); + if (conversation == null) { + return; + } + } else { + Account account; + try { + account = xmppConnectionService.findAccountByJid(Jid.fromString(share.account)); + } catch (final InvalidJidException e) { + account = null; + } + if (account == null) { + return; + } + + try { + conversation = xmppConnectionService + .findOrCreateConversation(account, Jid.fromString(share.contact), false); + } catch (final InvalidJidException e) { + return; + } + } ContactChosen = true; - share(conversation); - } - - private void share(final Conversation conversation) { - final Account account = conversation.getAccount(); - final XmppConnection connection = account.getXmppConnection(); - final long max = connection == null ? -1 : connection.getFeatures().getMaxHttpUploadSize(); - mListView.setEnabled(false); - if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP && !hasPgp()) { - if (share.uuid == null) { - showInstallPgpDialog(); - } else { - Toast.makeText(this,R.string.openkeychain_not_installed,Toast.LENGTH_SHORT).show(); - finish(); - } - return; - } - if (share.uris.size() != 0) { - OnPresenceSelected callback = new OnPresenceSelected() { - @Override - public void onPresenceSelected() { - attachmentCounter.set(share.uris.size()); - if (share.image) { + share(conversation); + } + + private void share(final Conversation conversation) { + final Account account = conversation.getAccount(); + final XmppConnection connection = account.getXmppConnection(); + final long max = connection == null ? -1 : connection.getFeatures().getMaxHttpUploadSize(); + mListView.setEnabled(false); + if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP && !hasPgp()) { + if (share.uuid == null) { + showInstallPgpDialog(); + } else { + Toast.makeText(this, R.string.openkeychain_not_installed, Toast.LENGTH_SHORT).show(); + finish(); + } + return; + } + if (share.uris.size() != 0) { + OnPresenceSelected callback = new OnPresenceSelected() { + @Override + public void onPresenceSelected() { + attachmentCounter.set(share.uris.size()); + if (share.image) { Log.d(Config.LOGTAG, "ShareWithActivity share() image " + share.uris.size() + " uri(s) " + share.uris.toString()); share.multiple = share.uris.size() > 1; replaceToast(getString(share.multiple ? R.string.preparing_images : R.string.preparing_image)); @@ -347,49 +347,49 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer ShareWithActivity.this.xmppConnectionService .attachVideoToConversation(conversation, share.uris.get(0), attachFileCallback); - } else { + } else { Log.d(Config.LOGTAG, "ShareWithActivity share() file " + share.uris.size() + " uri(s) " + share.uris.toString()); - replaceToast(getString(R.string.preparing_file)); - ShareWithActivity.this.xmppConnectionService - .attachFileToConversation(conversation, share.uris.get(0), - attachFileCallback); - } - } - }; - if (account.httpUploadAvailable() - && ((share.image && !neverCompressPictures()) - || share.video - || conversation.getMode() == Conversation.MODE_MULTI - || FileBackend.allFilesUnderSize(this, share.uris, max)) - && conversation.getNextEncryption() != Message.ENCRYPTION_OTR) { - callback.onPresenceSelected(); - } else { - 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 { + replaceToast(getString(R.string.preparing_file)); + ShareWithActivity.this.xmppConnectionService + .attachFileToConversation(conversation, share.uris.get(0), + attachFileCallback); + } + } + }; + if (account.httpUploadAvailable() + && ((share.image && !neverCompressPictures()) + || share.video + || conversation.getMode() == Conversation.MODE_MULTI + || FileBackend.allFilesUnderSize(this, share.uris, max)) + && conversation.getNextEncryption() != Message.ENCRYPTION_OTR) { + callback.onPresenceSelected(); + } else { + 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()); + 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 { + 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()); } @@ -403,22 +403,22 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer } else { callback.onPresenceSelected(); } - } - } - - } - - public void refreshUiReal() { - xmppConnectionService.populateWithOrderedConversations(mConversations, this.share != null && this.share.uris.size() == 0); - mAdapter.notifyDataSetChanged(); - } - - @Override - public void onBackPressed() { - if (attachmentCounter.get() >= 1) { - replaceToast(getString(R.string.sharing_files_please_wait)); - } else { - super.onBackPressed(); - } - } + } + } + + } + + public void refreshUiReal() { + xmppConnectionService.populateWithOrderedConversations(mConversations, this.share != null && this.share.uris.size() == 0); + mAdapter.notifyDataSetChanged(); + } + + @Override + public void onBackPressed() { + if (attachmentCounter.get() >= 1) { + replaceToast(getString(R.string.sharing_files_please_wait)); + } else { + super.onBackPressed(); + } + } } diff --git a/src/main/java/de/pixart/messenger/ui/ShowFullscreenMessageActivity.java b/src/main/java/de/pixart/messenger/ui/ShowFullscreenMessageActivity.java index ffa9018ec..faddebb86 100644 --- a/src/main/java/de/pixart/messenger/ui/ShowFullscreenMessageActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ShowFullscreenMessageActivity.java @@ -162,7 +162,7 @@ public class ShowFullscreenMessageActivity extends Activity { } private void DisplayVideo(Uri uri) { - MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + MediaMetadataRetriever retriever = new MediaMetadataRetriever(); retriever.setDataSource(uri.getPath()); height = Integer.valueOf(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)); width = Integer.valueOf(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)); @@ -217,7 +217,7 @@ public class ShowFullscreenMessageActivity extends Activity { } @Override - public void onStop () { + public void onStop() { mVideo.reset(); WindowManager.LayoutParams layout = getWindow().getAttributes(); layout.screenBrightness = -1; diff --git a/src/main/java/de/pixart/messenger/ui/ShowLocationActivity.java b/src/main/java/de/pixart/messenger/ui/ShowLocationActivity.java index 40ff08d80..bb950a21e 100644 --- a/src/main/java/de/pixart/messenger/ui/ShowLocationActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ShowLocationActivity.java @@ -35,9 +35,9 @@ import de.pixart.messenger.R; public class ShowLocationActivity extends Activity implements OnMapReadyCallback { - private GoogleMap mGoogleMap; - private LatLng mLocation; - private String mLocationName; + private GoogleMap mGoogleMap; + private LatLng mLocation; + private String mLocationName; private MarkerOptions options; private Marker marker; @@ -66,26 +66,26 @@ public class ShowLocationActivity extends Activity implements OnMapReadyCallback } } - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); if (getActionBar() != null) { getActionBar().setHomeButtonEnabled(true); getActionBar().setDisplayHomeAsUpEnabled(true); } - setContentView(R.layout.activity_show_locaction); - MapFragment fragment = (MapFragment) getFragmentManager().findFragmentById(R.id.map_fragment); - fragment.getMapAsync(this); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - finish(); - return true; + setContentView(R.layout.activity_show_locaction); + MapFragment fragment = (MapFragment) getFragmentManager().findFragmentById(R.id.map_fragment); + fragment.getMapAsync(this); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + finish(); + return true; case R.id.action_navigate: double longitude = mLocation.longitude; double latitude = mLocation.latitude; @@ -96,9 +96,9 @@ public class ShowLocationActivity extends Activity implements OnMapReadyCallback Toast.makeText(this, R.string.no_application_found_to_display_location, Toast.LENGTH_SHORT).show(); } return true; - } - return super.onOptionsItemSelected(item); - } + } + return super.onOptionsItemSelected(item); + } @Override public boolean onCreateOptionsMenu(Menu menu) { @@ -107,31 +107,31 @@ public class ShowLocationActivity extends Activity implements OnMapReadyCallback return true; } - @Override - protected void onResume() { - super.onResume(); - Intent intent = getIntent(); - - this.mLocationName = intent != null ? intent.getStringExtra("name") : null; - - if (intent != null && intent.hasExtra("longitude") && intent.hasExtra("latitude")) { - double longitude = intent.getDoubleExtra("longitude",0); - double latitude = intent.getDoubleExtra("latitude",0); - this.mLocation = new LatLng(latitude,longitude); - if (this.mGoogleMap != null) { - markAndCenterOnLocation(this.mLocation, this.mLocationName); - } - } - } - - @Override - protected void onPause() { - super.onPause(); - } - - @Override - public void onMapReady(GoogleMap googleMap) { - this.mGoogleMap = googleMap; + @Override + protected void onResume() { + super.onResume(); + Intent intent = getIntent(); + + this.mLocationName = intent != null ? intent.getStringExtra("name") : null; + + if (intent != null && intent.hasExtra("longitude") && intent.hasExtra("latitude")) { + double longitude = intent.getDoubleExtra("longitude", 0); + double latitude = intent.getDoubleExtra("latitude", 0); + this.mLocation = new LatLng(latitude, longitude); + if (this.mGoogleMap != null) { + markAndCenterOnLocation(this.mLocation, this.mLocationName); + } + } + } + + @Override + protected void onPause() { + super.onPause(); + } + + @Override + public void onMapReady(GoogleMap googleMap) { + this.mGoogleMap = googleMap; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED || checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) { @@ -142,10 +142,10 @@ public class ShowLocationActivity extends Activity implements OnMapReadyCallback this.mGoogleMap.setBuildingsEnabled(true); this.mGoogleMap.setMyLocationEnabled(true); } - if (this.mLocation != null) { - this.markAndCenterOnLocation(this.mLocation,this.mLocationName); - } - } + if (this.mLocation != null) { + this.markAndCenterOnLocation(this.mLocation, this.mLocationName); + } + } private static String getAddress(Context context, LatLng location) { double longitude = location.longitude; @@ -162,7 +162,7 @@ public class ShowLocationActivity extends Activity implements OnMapReadyCallback strAddress.append(Address.getAddressLine(i)).append("\n"); } address = strAddress.toString(); - address = address.substring(0, address.length()-1); //trim last \n + address = address.substring(0, address.length() - 1); //trim last \n } } catch (Exception e) { diff --git a/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java b/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java index cd64149cd..46beddcde 100644 --- a/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java +++ b/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java @@ -843,7 +843,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU private boolean handleJid(Invite invite) { Account account = xmppConnectionService.findAccountByJid(invite.getJid()); if (account != null && invite.hasFingerprints()) { - if (xmppConnectionService.verifyFingerprints(account,invite.getFingerprints())) { + if (xmppConnectionService.verifyFingerprints(account, invite.getFingerprints())) { switchToAccount(account); finish(); return true; @@ -865,7 +865,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU } else if (contacts.size() == 1) { Contact contact = contacts.get(0); if (invite.hasFingerprints()) { - xmppConnectionService.verifyFingerprints(contact,invite.getFingerprints()); + xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints()); } switchToConversation(contact); return true; diff --git a/src/main/java/de/pixart/messenger/ui/StartUI.java b/src/main/java/de/pixart/messenger/ui/StartUI.java index 5c9e4c324..18a5d0f0d 100644 --- a/src/main/java/de/pixart/messenger/ui/StartUI.java +++ b/src/main/java/de/pixart/messenger/ui/StartUI.java @@ -43,8 +43,8 @@ public class StartUI extends AppCompatActivity long FirstStartTime = FirstStart.getLong("FirstStart", 0); if (EasyPermissions.hasPermissions(this, perms)) { // Already have permission, start ConversationsActivity - Log.d(Config.LOGTAG, "All permissions granted, starting "+getString(R.string.app_name) + "(" +FirstStartTime + ")"); - Intent intent = new Intent (this, ConversationActivity.class); + Log.d(Config.LOGTAG, "All permissions granted, starting " + getString(R.string.app_name) + "(" + FirstStartTime + ")"); + Intent intent = new Intent(this, ConversationActivity.class); intent.putExtra("FirstStart", FirstStartTime); startActivity(intent); finish(); diff --git a/src/main/java/de/pixart/messenger/ui/TimePreference.java b/src/main/java/de/pixart/messenger/ui/TimePreference.java index 1c8c8c0f1..731a3bb50 100644 --- a/src/main/java/de/pixart/messenger/ui/TimePreference.java +++ b/src/main/java/de/pixart/messenger/ui/TimePreference.java @@ -13,93 +13,93 @@ import java.util.Calendar; import java.util.Date; public class TimePreference extends DialogPreference implements Preference.OnPreferenceChangeListener { - private TimePicker picker = null; - public final static long DEFAULT_VALUE = 0; - - public TimePreference(final Context context, final AttributeSet attrs) { - super(context, attrs, 0); - this.setOnPreferenceChangeListener(this); - } - - protected void setTime(final long time) { - persistLong(time); - notifyDependencyChange(shouldDisableDependents()); - notifyChanged(); - } - - protected void updateSummary(final long time) { - final DateFormat dateFormat = android.text.format.DateFormat.getTimeFormat(getContext()); - final Date date = new Date(time); - setSummary(dateFormat.format(date.getTime())); - } - - @Override - protected View onCreateDialogView() { - picker = new TimePicker(getContext()); - picker.setIs24HourView(android.text.format.DateFormat.is24HourFormat(getContext())); - return picker; - } - - protected Calendar getPersistedTime() { - final Calendar c = Calendar.getInstance(); - c.setTimeInMillis(getPersistedLong(DEFAULT_VALUE)); - - return c; - } - - @SuppressWarnings("NullableProblems") - @Override - protected void onBindDialogView(final View v) { - super.onBindDialogView(v); - final Calendar c = getPersistedTime(); - - picker.setCurrentHour(c.get(Calendar.HOUR_OF_DAY)); - picker.setCurrentMinute(c.get(Calendar.MINUTE)); - } - - @Override - protected void onDialogClosed(final boolean positiveResult) { - super.onDialogClosed(positiveResult); - - if (positiveResult) { - final Calendar c = Calendar.getInstance(); - c.set(Calendar.MINUTE, picker.getCurrentMinute()); - c.set(Calendar.HOUR_OF_DAY, picker.getCurrentHour()); - - - if (!callChangeListener(c.getTimeInMillis())) { - return; - } - - setTime(c.getTimeInMillis()); - } - } - - @Override - protected Object onGetDefaultValue(final TypedArray a, final int index) { - return a.getInteger(index, 0); - } - - @Override - protected void onSetInitialValue(final boolean restorePersistedValue, final Object defaultValue) { - long time; - if (defaultValue == null) { - time = restorePersistedValue ? getPersistedLong(DEFAULT_VALUE) : DEFAULT_VALUE; - } else if (defaultValue instanceof Long) { - time = restorePersistedValue ? getPersistedLong((Long) defaultValue) : (Long) defaultValue; - } else if (defaultValue instanceof Calendar) { - time = restorePersistedValue ? getPersistedLong(((Calendar)defaultValue).getTimeInMillis()) : ((Calendar)defaultValue).getTimeInMillis(); - } else { - time = restorePersistedValue ? getPersistedLong(DEFAULT_VALUE) : DEFAULT_VALUE; - } - - setTime(time); - updateSummary(time); - } - - @Override - public boolean onPreferenceChange(final Preference preference, final Object newValue) { - ((TimePreference) preference).updateSummary((Long)newValue); - return true; - } + private TimePicker picker = null; + public final static long DEFAULT_VALUE = 0; + + public TimePreference(final Context context, final AttributeSet attrs) { + super(context, attrs, 0); + this.setOnPreferenceChangeListener(this); + } + + protected void setTime(final long time) { + persistLong(time); + notifyDependencyChange(shouldDisableDependents()); + notifyChanged(); + } + + protected void updateSummary(final long time) { + final DateFormat dateFormat = android.text.format.DateFormat.getTimeFormat(getContext()); + final Date date = new Date(time); + setSummary(dateFormat.format(date.getTime())); + } + + @Override + protected View onCreateDialogView() { + picker = new TimePicker(getContext()); + picker.setIs24HourView(android.text.format.DateFormat.is24HourFormat(getContext())); + return picker; + } + + protected Calendar getPersistedTime() { + final Calendar c = Calendar.getInstance(); + c.setTimeInMillis(getPersistedLong(DEFAULT_VALUE)); + + return c; + } + + @SuppressWarnings("NullableProblems") + @Override + protected void onBindDialogView(final View v) { + super.onBindDialogView(v); + final Calendar c = getPersistedTime(); + + picker.setCurrentHour(c.get(Calendar.HOUR_OF_DAY)); + picker.setCurrentMinute(c.get(Calendar.MINUTE)); + } + + @Override + protected void onDialogClosed(final boolean positiveResult) { + super.onDialogClosed(positiveResult); + + if (positiveResult) { + final Calendar c = Calendar.getInstance(); + c.set(Calendar.MINUTE, picker.getCurrentMinute()); + c.set(Calendar.HOUR_OF_DAY, picker.getCurrentHour()); + + + if (!callChangeListener(c.getTimeInMillis())) { + return; + } + + setTime(c.getTimeInMillis()); + } + } + + @Override + protected Object onGetDefaultValue(final TypedArray a, final int index) { + return a.getInteger(index, 0); + } + + @Override + protected void onSetInitialValue(final boolean restorePersistedValue, final Object defaultValue) { + long time; + if (defaultValue == null) { + time = restorePersistedValue ? getPersistedLong(DEFAULT_VALUE) : DEFAULT_VALUE; + } else if (defaultValue instanceof Long) { + time = restorePersistedValue ? getPersistedLong((Long) defaultValue) : (Long) defaultValue; + } else if (defaultValue instanceof Calendar) { + time = restorePersistedValue ? getPersistedLong(((Calendar) defaultValue).getTimeInMillis()) : ((Calendar) defaultValue).getTimeInMillis(); + } else { + time = restorePersistedValue ? getPersistedLong(DEFAULT_VALUE) : DEFAULT_VALUE; + } + + setTime(time); + updateSummary(time); + } + + @Override + public boolean onPreferenceChange(final Preference preference, final Object newValue) { + ((TimePreference) preference).updateSummary((Long) newValue); + return true; + } } diff --git a/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java b/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java index 2f3951c3a..893317970 100644 --- a/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java +++ b/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java @@ -30,307 +30,307 @@ import de.pixart.messenger.xmpp.jid.InvalidJidException; import de.pixart.messenger.xmpp.jid.Jid; public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdated { - private List contactJids; - - private Account mAccount; - private Conversation mConversation; - private TextView keyErrorMessage; - private LinearLayout keyErrorMessageCard; - private TextView ownKeysTitle; - private LinearLayout ownKeys; - private LinearLayout ownKeysCard; - private LinearLayout foreignKeys; - private Button mSaveButton; - private Button mCancelButton; - - private AxolotlService.FetchStatus lastFetchReport = AxolotlService.FetchStatus.SUCCESS; - - private final Map ownKeysToTrust = new HashMap<>(); - private final Map> foreignKeysToTrust = new HashMap<>(); - - private final OnClickListener mSaveButtonListener = new OnClickListener() { - @Override - public void onClick(View v) { - commitTrusts(); - finishOk(); - } - }; - - private final OnClickListener mCancelButtonListener = new OnClickListener() { - @Override - public void onClick(View v) { - setResult(RESULT_CANCELED); - finish(); - } - }; - - @Override - protected void refreshUiReal() { - invalidateOptionsMenu(); - populateView(); - } - - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_trust_keys); - this.contactJids = new ArrayList<>(); - for(String jid : getIntent().getStringArrayExtra("contacts")) { - try { - this.contactJids.add(Jid.fromString(jid)); - } catch (InvalidJidException e) { - e.printStackTrace(); - } - } - - keyErrorMessageCard = (LinearLayout) findViewById(R.id.key_error_message_card); - keyErrorMessage = (TextView) findViewById(R.id.key_error_message); - ownKeysTitle = (TextView) findViewById(R.id.own_keys_title); - ownKeys = (LinearLayout) findViewById(R.id.own_keys_details); - ownKeysCard = (LinearLayout) findViewById(R.id.own_keys_card); - foreignKeys = (LinearLayout) findViewById(R.id.foreign_keys); - mCancelButton = (Button) findViewById(R.id.cancel_button); - mCancelButton.setOnClickListener(mCancelButtonListener); - mSaveButton = (Button) findViewById(R.id.save_button); - mSaveButton.setOnClickListener(mSaveButtonListener); - - - if (getActionBar() != null) { - getActionBar().setHomeButtonEnabled(true); - getActionBar().setDisplayHomeAsUpEnabled(true); - } - } - - private void populateView() { - setTitle(getString(R.string.trust_omemo_fingerprints)); - ownKeys.removeAllViews(); - foreignKeys.removeAllViews(); - boolean hasOwnKeys = false; - boolean hasForeignKeys = false; - for(final String fingerprint : ownKeysToTrust.keySet()) { - hasOwnKeys = true; - addFingerprintRowWithListeners(ownKeys, mAccount, fingerprint, false, - FingerprintStatus.createActive(ownKeysToTrust.get(fingerprint)), false, false, - new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - ownKeysToTrust.put(fingerprint, isChecked); - // own fingerprints have no impact on locked status. - } - } - ); - } - - synchronized (this.foreignKeysToTrust) { - for (Map.Entry> entry : foreignKeysToTrust.entrySet()) { - hasForeignKeys = true; - final LinearLayout layout = (LinearLayout) getLayoutInflater().inflate(R.layout.keys_card, foreignKeys, false); - final Jid jid = entry.getKey(); - final TextView header = (TextView) layout.findViewById(R.id.foreign_keys_title); - final LinearLayout keysContainer = (LinearLayout) layout.findViewById(R.id.foreign_keys_details); - final TextView informNoKeys = (TextView) layout.findViewById(R.id.no_keys_to_accept); - header.setText(jid.toString()); - final Map fingerprints = entry.getValue(); - for (final String fingerprint : fingerprints.keySet()) { - addFingerprintRowWithListeners(keysContainer, mAccount, fingerprint, false, - FingerprintStatus.createActive(fingerprints.get(fingerprint)), false, false, - new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - fingerprints.put(fingerprint, isChecked); - lockOrUnlockAsNeeded(); - } - } - ); - } - if (fingerprints.size() == 0) { - informNoKeys.setVisibility(View.VISIBLE); - informNoKeys.setText(getString(R.string.no_keys_just_confirm,mAccount.getRoster().getContact(jid).getDisplayName())); - } else { - informNoKeys.setVisibility(View.GONE); - } - foreignKeys.addView(layout); - } - } - - ownKeysTitle.setText(mAccount.getJid().toBareJid().toString()); - ownKeysCard.setVisibility(hasOwnKeys ? View.VISIBLE : View.GONE); - foreignKeys.setVisibility(hasForeignKeys ? View.VISIBLE : View.GONE); - if(hasPendingKeyFetches()) { - setFetching(); - lock(); - } else { - if (!hasForeignKeys && hasNoOtherTrustedKeys()) { - keyErrorMessageCard.setVisibility(View.VISIBLE); - if (lastFetchReport == AxolotlService.FetchStatus.ERROR - || mAccount.getAxolotlService().fetchMapHasErrors(contactJids)) { - keyErrorMessage.setText(R.string.error_no_keys_to_trust_server_error); - } else { - keyErrorMessage.setText(R.string.error_no_keys_to_trust); - } - ownKeys.removeAllViews(); - ownKeysCard.setVisibility(View.GONE); - foreignKeys.removeAllViews(); - foreignKeys.setVisibility(View.GONE); - } - lockOrUnlockAsNeeded(); - setDone(); - } - } - - private boolean reloadFingerprints() { - List acceptedTargets = mConversation == null ? new ArrayList() : mConversation.getAcceptedCryptoTargets(); - ownKeysToTrust.clear(); - AxolotlService service = this.mAccount.getAxolotlService(); - Set ownKeysSet = service.getKeysWithTrust(FingerprintStatus.createActiveUndecided()); - for(final IdentityKey identityKey : ownKeysSet) { - if(!ownKeysToTrust.containsKey(identityKey)) { - ownKeysToTrust.put(identityKey.getFingerprint().replaceAll("\\s", ""), false); - } - } - synchronized (this.foreignKeysToTrust) { - foreignKeysToTrust.clear(); - for (Jid jid : contactJids) { - Set foreignKeysSet = service.getKeysWithTrust(FingerprintStatus.createActiveUndecided(), jid); - if (hasNoOtherTrustedKeys(jid) && ownKeysSet.size() == 0) { - foreignKeysSet.addAll(service.getKeysWithTrust(FingerprintStatus.createActive(false), jid)); - } - Map foreignFingerprints = new HashMap<>(); - for (final IdentityKey identityKey : foreignKeysSet) { - if (!foreignFingerprints.containsKey(identityKey)) { - foreignFingerprints.put(identityKey.getFingerprint().replaceAll("\\s", ""), false); - } - } - if (foreignFingerprints.size() > 0 || !acceptedTargets.contains(jid)) { - foreignKeysToTrust.put(jid, foreignFingerprints); - } - } - } - return ownKeysSet.size() + foreignKeysToTrust.size() > 0; - } - - public void onBackendConnected() { - Intent intent = getIntent(); - this.mAccount = extractAccount(intent); - if (this.mAccount != null && intent != null) { - String uuid = intent.getStringExtra("conversation"); - this.mConversation = xmppConnectionService.findConversationByUuid(uuid); - reloadFingerprints(); - populateView(); - } - } - - private boolean hasNoOtherTrustedKeys() { - return mAccount == null || mAccount.getAxolotlService().anyTargetHasNoTrustedKeys(contactJids); - } - - private boolean hasNoOtherTrustedKeys(Jid contact) { - return mAccount == null || mAccount.getAxolotlService().getNumTrustedKeys(contact) == 0; - } - - private boolean hasPendingKeyFetches() { - return mAccount != null && mAccount.getAxolotlService().hasPendingKeyFetches(mAccount, contactJids); - } - - - @Override - public void onKeyStatusUpdated(final AxolotlService.FetchStatus report) { - if (report != null) { - lastFetchReport = report; - runOnUiThread(new Runnable() { - @Override - public void run() { - switch (report) { - case ERROR: - Toast.makeText(TrustKeysActivity.this,R.string.error_fetching_omemo_key,Toast.LENGTH_SHORT).show(); - break; - case SUCCESS_VERIFIED: - Toast.makeText(TrustKeysActivity.this, - Config.X509_VERIFICATION ? R.string.verified_omemo_key_with_certificate : R.string.all_omemo_keys_have_been_verified, - Toast.LENGTH_LONG).show(); - break; - } - } - }); - - } - boolean keysToTrust = reloadFingerprints(); - if (keysToTrust || hasPendingKeyFetches() || hasNoOtherTrustedKeys()) { - refreshUi(); - } else { - runOnUiThread(new Runnable() { - @Override - public void run() { - finishOk(); - } - }); - - } - } - - private void finishOk() { - Intent data = new Intent(); - data.putExtra("choice", getIntent().getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID)); - setResult(RESULT_OK, data); - finish(); - } - - private void commitTrusts() { - for(final String fingerprint :ownKeysToTrust.keySet()) { - mAccount.getAxolotlService().setFingerprintTrust( - fingerprint, - FingerprintStatus.createActive(ownKeysToTrust.get(fingerprint))); - } - List acceptedTargets = mConversation == null ? new ArrayList() : mConversation.getAcceptedCryptoTargets(); - synchronized (this.foreignKeysToTrust) { - for (Map.Entry> entry : foreignKeysToTrust.entrySet()) { - Jid jid = entry.getKey(); - Map value = entry.getValue(); - if (!acceptedTargets.contains(jid)) { - acceptedTargets.add(jid); - } - for (final String fingerprint : value.keySet()) { - mAccount.getAxolotlService().setFingerprintTrust( - fingerprint, - FingerprintStatus.createActive(value.get(fingerprint))); - } - } - } - if (mConversation != null && mConversation.getMode() == Conversation.MODE_MULTI) { - mConversation.setAcceptedCryptoTargets(acceptedTargets); - xmppConnectionService.updateConversation(mConversation); - } - } - - private void unlock() { - mSaveButton.setEnabled(true); - mSaveButton.setTextColor(getPrimaryTextColor()); - } - - private void lock() { - mSaveButton.setEnabled(false); - mSaveButton.setTextColor(getSecondaryTextColor()); - } - - private void lockOrUnlockAsNeeded() { - synchronized (this.foreignKeysToTrust) { - for (Jid jid : contactJids) { - Map fingerprints = foreignKeysToTrust.get(jid); - if (hasNoOtherTrustedKeys(jid) && (fingerprints == null || !fingerprints.values().contains(true))) { - lock(); - return; - } - } - } - unlock(); - - } - - private void setDone() { - mSaveButton.setText(getString(R.string.done)); - } - - private void setFetching() { - mSaveButton.setText(getString(R.string.fetching_keys)); - } + private List contactJids; + + private Account mAccount; + private Conversation mConversation; + private TextView keyErrorMessage; + private LinearLayout keyErrorMessageCard; + private TextView ownKeysTitle; + private LinearLayout ownKeys; + private LinearLayout ownKeysCard; + private LinearLayout foreignKeys; + private Button mSaveButton; + private Button mCancelButton; + + private AxolotlService.FetchStatus lastFetchReport = AxolotlService.FetchStatus.SUCCESS; + + private final Map ownKeysToTrust = new HashMap<>(); + private final Map> foreignKeysToTrust = new HashMap<>(); + + private final OnClickListener mSaveButtonListener = new OnClickListener() { + @Override + public void onClick(View v) { + commitTrusts(); + finishOk(); + } + }; + + private final OnClickListener mCancelButtonListener = new OnClickListener() { + @Override + public void onClick(View v) { + setResult(RESULT_CANCELED); + finish(); + } + }; + + @Override + protected void refreshUiReal() { + invalidateOptionsMenu(); + populateView(); + } + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_trust_keys); + this.contactJids = new ArrayList<>(); + for (String jid : getIntent().getStringArrayExtra("contacts")) { + try { + this.contactJids.add(Jid.fromString(jid)); + } catch (InvalidJidException e) { + e.printStackTrace(); + } + } + + keyErrorMessageCard = (LinearLayout) findViewById(R.id.key_error_message_card); + keyErrorMessage = (TextView) findViewById(R.id.key_error_message); + ownKeysTitle = (TextView) findViewById(R.id.own_keys_title); + ownKeys = (LinearLayout) findViewById(R.id.own_keys_details); + ownKeysCard = (LinearLayout) findViewById(R.id.own_keys_card); + foreignKeys = (LinearLayout) findViewById(R.id.foreign_keys); + mCancelButton = (Button) findViewById(R.id.cancel_button); + mCancelButton.setOnClickListener(mCancelButtonListener); + mSaveButton = (Button) findViewById(R.id.save_button); + mSaveButton.setOnClickListener(mSaveButtonListener); + + + if (getActionBar() != null) { + getActionBar().setHomeButtonEnabled(true); + getActionBar().setDisplayHomeAsUpEnabled(true); + } + } + + private void populateView() { + setTitle(getString(R.string.trust_omemo_fingerprints)); + ownKeys.removeAllViews(); + foreignKeys.removeAllViews(); + boolean hasOwnKeys = false; + boolean hasForeignKeys = false; + for (final String fingerprint : ownKeysToTrust.keySet()) { + hasOwnKeys = true; + addFingerprintRowWithListeners(ownKeys, mAccount, fingerprint, false, + FingerprintStatus.createActive(ownKeysToTrust.get(fingerprint)), false, false, + new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + ownKeysToTrust.put(fingerprint, isChecked); + // own fingerprints have no impact on locked status. + } + } + ); + } + + synchronized (this.foreignKeysToTrust) { + for (Map.Entry> entry : foreignKeysToTrust.entrySet()) { + hasForeignKeys = true; + final LinearLayout layout = (LinearLayout) getLayoutInflater().inflate(R.layout.keys_card, foreignKeys, false); + final Jid jid = entry.getKey(); + final TextView header = (TextView) layout.findViewById(R.id.foreign_keys_title); + final LinearLayout keysContainer = (LinearLayout) layout.findViewById(R.id.foreign_keys_details); + final TextView informNoKeys = (TextView) layout.findViewById(R.id.no_keys_to_accept); + header.setText(jid.toString()); + final Map fingerprints = entry.getValue(); + for (final String fingerprint : fingerprints.keySet()) { + addFingerprintRowWithListeners(keysContainer, mAccount, fingerprint, false, + FingerprintStatus.createActive(fingerprints.get(fingerprint)), false, false, + new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + fingerprints.put(fingerprint, isChecked); + lockOrUnlockAsNeeded(); + } + } + ); + } + if (fingerprints.size() == 0) { + informNoKeys.setVisibility(View.VISIBLE); + informNoKeys.setText(getString(R.string.no_keys_just_confirm, mAccount.getRoster().getContact(jid).getDisplayName())); + } else { + informNoKeys.setVisibility(View.GONE); + } + foreignKeys.addView(layout); + } + } + + ownKeysTitle.setText(mAccount.getJid().toBareJid().toString()); + ownKeysCard.setVisibility(hasOwnKeys ? View.VISIBLE : View.GONE); + foreignKeys.setVisibility(hasForeignKeys ? View.VISIBLE : View.GONE); + if (hasPendingKeyFetches()) { + setFetching(); + lock(); + } else { + if (!hasForeignKeys && hasNoOtherTrustedKeys()) { + keyErrorMessageCard.setVisibility(View.VISIBLE); + if (lastFetchReport == AxolotlService.FetchStatus.ERROR + || mAccount.getAxolotlService().fetchMapHasErrors(contactJids)) { + keyErrorMessage.setText(R.string.error_no_keys_to_trust_server_error); + } else { + keyErrorMessage.setText(R.string.error_no_keys_to_trust); + } + ownKeys.removeAllViews(); + ownKeysCard.setVisibility(View.GONE); + foreignKeys.removeAllViews(); + foreignKeys.setVisibility(View.GONE); + } + lockOrUnlockAsNeeded(); + setDone(); + } + } + + private boolean reloadFingerprints() { + List acceptedTargets = mConversation == null ? new ArrayList() : mConversation.getAcceptedCryptoTargets(); + ownKeysToTrust.clear(); + AxolotlService service = this.mAccount.getAxolotlService(); + Set ownKeysSet = service.getKeysWithTrust(FingerprintStatus.createActiveUndecided()); + for (final IdentityKey identityKey : ownKeysSet) { + if (!ownKeysToTrust.containsKey(identityKey)) { + ownKeysToTrust.put(identityKey.getFingerprint().replaceAll("\\s", ""), false); + } + } + synchronized (this.foreignKeysToTrust) { + foreignKeysToTrust.clear(); + for (Jid jid : contactJids) { + Set foreignKeysSet = service.getKeysWithTrust(FingerprintStatus.createActiveUndecided(), jid); + if (hasNoOtherTrustedKeys(jid) && ownKeysSet.size() == 0) { + foreignKeysSet.addAll(service.getKeysWithTrust(FingerprintStatus.createActive(false), jid)); + } + Map foreignFingerprints = new HashMap<>(); + for (final IdentityKey identityKey : foreignKeysSet) { + if (!foreignFingerprints.containsKey(identityKey)) { + foreignFingerprints.put(identityKey.getFingerprint().replaceAll("\\s", ""), false); + } + } + if (foreignFingerprints.size() > 0 || !acceptedTargets.contains(jid)) { + foreignKeysToTrust.put(jid, foreignFingerprints); + } + } + } + return ownKeysSet.size() + foreignKeysToTrust.size() > 0; + } + + public void onBackendConnected() { + Intent intent = getIntent(); + this.mAccount = extractAccount(intent); + if (this.mAccount != null && intent != null) { + String uuid = intent.getStringExtra("conversation"); + this.mConversation = xmppConnectionService.findConversationByUuid(uuid); + reloadFingerprints(); + populateView(); + } + } + + private boolean hasNoOtherTrustedKeys() { + return mAccount == null || mAccount.getAxolotlService().anyTargetHasNoTrustedKeys(contactJids); + } + + private boolean hasNoOtherTrustedKeys(Jid contact) { + return mAccount == null || mAccount.getAxolotlService().getNumTrustedKeys(contact) == 0; + } + + private boolean hasPendingKeyFetches() { + return mAccount != null && mAccount.getAxolotlService().hasPendingKeyFetches(mAccount, contactJids); + } + + + @Override + public void onKeyStatusUpdated(final AxolotlService.FetchStatus report) { + if (report != null) { + lastFetchReport = report; + runOnUiThread(new Runnable() { + @Override + public void run() { + switch (report) { + case ERROR: + Toast.makeText(TrustKeysActivity.this, R.string.error_fetching_omemo_key, Toast.LENGTH_SHORT).show(); + break; + case SUCCESS_VERIFIED: + Toast.makeText(TrustKeysActivity.this, + Config.X509_VERIFICATION ? R.string.verified_omemo_key_with_certificate : R.string.all_omemo_keys_have_been_verified, + Toast.LENGTH_LONG).show(); + break; + } + } + }); + + } + boolean keysToTrust = reloadFingerprints(); + if (keysToTrust || hasPendingKeyFetches() || hasNoOtherTrustedKeys()) { + refreshUi(); + } else { + runOnUiThread(new Runnable() { + @Override + public void run() { + finishOk(); + } + }); + + } + } + + private void finishOk() { + Intent data = new Intent(); + data.putExtra("choice", getIntent().getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID)); + setResult(RESULT_OK, data); + finish(); + } + + private void commitTrusts() { + for (final String fingerprint : ownKeysToTrust.keySet()) { + mAccount.getAxolotlService().setFingerprintTrust( + fingerprint, + FingerprintStatus.createActive(ownKeysToTrust.get(fingerprint))); + } + List acceptedTargets = mConversation == null ? new ArrayList() : mConversation.getAcceptedCryptoTargets(); + synchronized (this.foreignKeysToTrust) { + for (Map.Entry> entry : foreignKeysToTrust.entrySet()) { + Jid jid = entry.getKey(); + Map value = entry.getValue(); + if (!acceptedTargets.contains(jid)) { + acceptedTargets.add(jid); + } + for (final String fingerprint : value.keySet()) { + mAccount.getAxolotlService().setFingerprintTrust( + fingerprint, + FingerprintStatus.createActive(value.get(fingerprint))); + } + } + } + if (mConversation != null && mConversation.getMode() == Conversation.MODE_MULTI) { + mConversation.setAcceptedCryptoTargets(acceptedTargets); + xmppConnectionService.updateConversation(mConversation); + } + } + + private void unlock() { + mSaveButton.setEnabled(true); + mSaveButton.setTextColor(getPrimaryTextColor()); + } + + private void lock() { + mSaveButton.setEnabled(false); + mSaveButton.setTextColor(getSecondaryTextColor()); + } + + private void lockOrUnlockAsNeeded() { + synchronized (this.foreignKeysToTrust) { + for (Jid jid : contactJids) { + Map fingerprints = foreignKeysToTrust.get(jid); + if (hasNoOtherTrustedKeys(jid) && (fingerprints == null || !fingerprints.values().contains(true))) { + lock(); + return; + } + } + } + unlock(); + + } + + private void setDone() { + mSaveButton.setText(getString(R.string.done)); + } + + private void setFetching() { + mSaveButton.setText(getString(R.string.fetching_keys)); + } } diff --git a/src/main/java/de/pixart/messenger/ui/UiCallback.java b/src/main/java/de/pixart/messenger/ui/UiCallback.java index 07377c879..310a49255 100644 --- a/src/main/java/de/pixart/messenger/ui/UiCallback.java +++ b/src/main/java/de/pixart/messenger/ui/UiCallback.java @@ -3,9 +3,9 @@ package de.pixart.messenger.ui; import android.app.PendingIntent; public interface UiCallback { - void success(T object); + void success(T object); - void error(int errorCode, T object); + void error(int errorCode, T object); - void userInputRequried(PendingIntent pi, T object); + void userInputRequried(PendingIntent pi, T object); } diff --git a/src/main/java/de/pixart/messenger/ui/VerifyOTRActivity.java b/src/main/java/de/pixart/messenger/ui/VerifyOTRActivity.java index bee5ab692..1bd8e20f7 100644 --- a/src/main/java/de/pixart/messenger/ui/VerifyOTRActivity.java +++ b/src/main/java/de/pixart/messenger/ui/VerifyOTRActivity.java @@ -31,414 +31,414 @@ import de.pixart.messenger.xmpp.jid.Jid; public class VerifyOTRActivity extends XmppActivity implements XmppConnectionService.OnConversationUpdate { - public static final String ACTION_VERIFY_CONTACT = "verify_contact"; - public static final int MODE_SCAN_FINGERPRINT = - 0x0502; - public static final int MODE_ASK_QUESTION = 0x0503; - public static final int MODE_ANSWER_QUESTION = 0x0504; - public static final int MODE_MANUAL_VERIFICATION = 0x0505; + public static final String ACTION_VERIFY_CONTACT = "verify_contact"; + public static final int MODE_SCAN_FINGERPRINT = -0x0502; + public static final int MODE_ASK_QUESTION = 0x0503; + public static final int MODE_ANSWER_QUESTION = 0x0504; + public static final int MODE_MANUAL_VERIFICATION = 0x0505; - private LinearLayout mManualVerificationArea; - private LinearLayout mSmpVerificationArea; - private TextView mRemoteFingerprint; - private TextView mYourFingerprint; - private TextView mVerificationExplain; - private TextView mStatusMessage; - private TextView mSharedSecretHint; - private EditText mSharedSecretHintEditable; - private EditText mSharedSecretSecret; - private Button mLeftButton; - private Button mRightButton; - private Account mAccount; - private Conversation mConversation; - private int mode = MODE_MANUAL_VERIFICATION; - private XmppUri mPendingUri = null; + private LinearLayout mManualVerificationArea; + private LinearLayout mSmpVerificationArea; + private TextView mRemoteFingerprint; + private TextView mYourFingerprint; + private TextView mVerificationExplain; + private TextView mStatusMessage; + private TextView mSharedSecretHint; + private EditText mSharedSecretHintEditable; + private EditText mSharedSecretSecret; + private Button mLeftButton; + private Button mRightButton; + private Account mAccount; + private Conversation mConversation; + private int mode = MODE_MANUAL_VERIFICATION; + private XmppUri mPendingUri = null; - private DialogInterface.OnClickListener mVerifyFingerprintListener = new DialogInterface.OnClickListener() { + private DialogInterface.OnClickListener mVerifyFingerprintListener = new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int click) { - mConversation.verifyOtrFingerprint(); - xmppConnectionService.syncRosterToDisk(mConversation.getAccount()); - Toast.makeText(VerifyOTRActivity.this,R.string.verified,Toast.LENGTH_SHORT).show(); - finish(); - } - }; + @Override + public void onClick(DialogInterface dialogInterface, int click) { + mConversation.verifyOtrFingerprint(); + xmppConnectionService.syncRosterToDisk(mConversation.getAccount()); + Toast.makeText(VerifyOTRActivity.this, R.string.verified, Toast.LENGTH_SHORT).show(); + finish(); + } + }; - private View.OnClickListener mCreateSharedSecretListener = new View.OnClickListener() { - @Override - public void onClick(final View view) { - if (isAccountOnline()) { - final String question = mSharedSecretHintEditable.getText().toString(); - final String secret = mSharedSecretSecret.getText().toString(); - if (question.trim().isEmpty()) { - mSharedSecretHintEditable.requestFocus(); - mSharedSecretHintEditable.setError(getString(R.string.shared_secret_hint_should_not_be_empty)); - } else if (secret.trim().isEmpty()) { - mSharedSecretSecret.requestFocus(); - mSharedSecretSecret.setError(getString(R.string.shared_secret_can_not_be_empty)); - } else { - mSharedSecretSecret.setError(null); - mSharedSecretHintEditable.setError(null); - initSmp(question, secret); - updateView(); - } - } - } - }; - private View.OnClickListener mCancelSharedSecretListener = new View.OnClickListener() { - @Override - public void onClick(View view) { - if (isAccountOnline()) { - abortSmp(); - updateView(); - } - } - }; - private View.OnClickListener mRespondSharedSecretListener = new View.OnClickListener() { + private View.OnClickListener mCreateSharedSecretListener = new View.OnClickListener() { + @Override + public void onClick(final View view) { + if (isAccountOnline()) { + final String question = mSharedSecretHintEditable.getText().toString(); + final String secret = mSharedSecretSecret.getText().toString(); + if (question.trim().isEmpty()) { + mSharedSecretHintEditable.requestFocus(); + mSharedSecretHintEditable.setError(getString(R.string.shared_secret_hint_should_not_be_empty)); + } else if (secret.trim().isEmpty()) { + mSharedSecretSecret.requestFocus(); + mSharedSecretSecret.setError(getString(R.string.shared_secret_can_not_be_empty)); + } else { + mSharedSecretSecret.setError(null); + mSharedSecretHintEditable.setError(null); + initSmp(question, secret); + updateView(); + } + } + } + }; + private View.OnClickListener mCancelSharedSecretListener = new View.OnClickListener() { + @Override + public void onClick(View view) { + if (isAccountOnline()) { + abortSmp(); + updateView(); + } + } + }; + private View.OnClickListener mRespondSharedSecretListener = new View.OnClickListener() { - @Override - public void onClick(View view) { - if (isAccountOnline()) { - final String question = mSharedSecretHintEditable.getText().toString(); - final String secret = mSharedSecretSecret.getText().toString(); - respondSmp(question, secret); - updateView(); - } - } - }; - private View.OnClickListener mRetrySharedSecretListener = new View.OnClickListener() { - @Override - public void onClick(View view) { - mConversation.smp().status = Conversation.Smp.STATUS_NONE; - mConversation.smp().hint = null; - mConversation.smp().secret = null; - updateView(); - } - }; - private View.OnClickListener mFinishListener = new View.OnClickListener() { - @Override - public void onClick(View view) { - mConversation.smp().status = Conversation.Smp.STATUS_NONE; - finish(); - } - }; + @Override + public void onClick(View view) { + if (isAccountOnline()) { + final String question = mSharedSecretHintEditable.getText().toString(); + final String secret = mSharedSecretSecret.getText().toString(); + respondSmp(question, secret); + updateView(); + } + } + }; + private View.OnClickListener mRetrySharedSecretListener = new View.OnClickListener() { + @Override + public void onClick(View view) { + mConversation.smp().status = Conversation.Smp.STATUS_NONE; + mConversation.smp().hint = null; + mConversation.smp().secret = null; + updateView(); + } + }; + private View.OnClickListener mFinishListener = new View.OnClickListener() { + @Override + public void onClick(View view) { + mConversation.smp().status = Conversation.Smp.STATUS_NONE; + finish(); + } + }; - protected boolean initSmp(final String question, final String secret) { - final Session session = mConversation.getOtrSession(); - if (session!=null) { - try { - session.initSmp(question, secret); - mConversation.smp().status = Conversation.Smp.STATUS_WE_REQUESTED; - mConversation.smp().secret = secret; - mConversation.smp().hint = question; - return true; - } catch (OtrException e) { - return false; - } - } else { - return false; - } - } + protected boolean initSmp(final String question, final String secret) { + final Session session = mConversation.getOtrSession(); + if (session != null) { + try { + session.initSmp(question, secret); + mConversation.smp().status = Conversation.Smp.STATUS_WE_REQUESTED; + mConversation.smp().secret = secret; + mConversation.smp().hint = question; + return true; + } catch (OtrException e) { + return false; + } + } else { + return false; + } + } - protected boolean abortSmp() { - final Session session = mConversation.getOtrSession(); - if (session!=null) { - try { - session.abortSmp(); - mConversation.smp().status = Conversation.Smp.STATUS_NONE; - mConversation.smp().hint = null; - mConversation.smp().secret = null; - return true; - } catch (OtrException e) { - return false; - } - } else { - return false; - } - } + protected boolean abortSmp() { + final Session session = mConversation.getOtrSession(); + if (session != null) { + try { + session.abortSmp(); + mConversation.smp().status = Conversation.Smp.STATUS_NONE; + mConversation.smp().hint = null; + mConversation.smp().secret = null; + return true; + } catch (OtrException e) { + return false; + } + } else { + return false; + } + } - protected boolean respondSmp(final String question, final String secret) { - final Session session = mConversation.getOtrSession(); - if (session!=null) { - try { - session.respondSmp(question,secret); - return true; - } catch (OtrException e) { - return false; - } - } else { - return false; - } - } + protected boolean respondSmp(final String question, final String secret) { + final Session session = mConversation.getOtrSession(); + if (session != null) { + try { + session.respondSmp(question, secret); + return true; + } catch (OtrException e) { + return false; + } + } else { + return false; + } + } - protected boolean verifyWithUri(XmppUri uri) { - Contact contact = mConversation.getContact(); - if (this.mConversation.getContact().getJid().equals(uri.getJid()) && uri.hasFingerprints()) { - xmppConnectionService.verifyFingerprints(contact,uri.getFingerprints()); - Toast.makeText(this,R.string.verified,Toast.LENGTH_SHORT).show(); - updateView(); - return true; - } else { - Toast.makeText(this,R.string.could_not_verify_fingerprint,Toast.LENGTH_SHORT).show(); - return false; - } - } + protected boolean verifyWithUri(XmppUri uri) { + Contact contact = mConversation.getContact(); + if (this.mConversation.getContact().getJid().equals(uri.getJid()) && uri.hasFingerprints()) { + xmppConnectionService.verifyFingerprints(contact, uri.getFingerprints()); + Toast.makeText(this, R.string.verified, Toast.LENGTH_SHORT).show(); + updateView(); + return true; + } else { + Toast.makeText(this, R.string.could_not_verify_fingerprint, Toast.LENGTH_SHORT).show(); + return false; + } + } - protected boolean isAccountOnline() { - if (this.mAccount.getStatus() != Account.State.ONLINE) { - Toast.makeText(this,R.string.not_connected_try_again,Toast.LENGTH_SHORT).show(); - return false; - } else { - return true; - } - } + protected boolean isAccountOnline() { + if (this.mAccount.getStatus() != Account.State.ONLINE) { + Toast.makeText(this, R.string.not_connected_try_again, Toast.LENGTH_SHORT).show(); + return false; + } else { + return true; + } + } - protected boolean handleIntent(Intent intent) { - if (intent != null && intent.getAction().equals(ACTION_VERIFY_CONTACT)) { - this.mAccount = extractAccount(intent); - if (this.mAccount == null) { - return false; - } - try { - this.mConversation = this.xmppConnectionService.find(this.mAccount,Jid.fromString(intent.getExtras().getString("contact"))); - if (this.mConversation == null) { - return false; - } - } catch (final InvalidJidException ignored) { - return false; - } - this.mode = intent.getIntExtra("mode", MODE_MANUAL_VERIFICATION); - if (this.mode == MODE_SCAN_FINGERPRINT) { - new IntentIntegrator(this).initiateScan(); - return false; - } - return true; - } else { - return false; - } - } + protected boolean handleIntent(Intent intent) { + if (intent != null && intent.getAction().equals(ACTION_VERIFY_CONTACT)) { + this.mAccount = extractAccount(intent); + if (this.mAccount == null) { + return false; + } + try { + this.mConversation = this.xmppConnectionService.find(this.mAccount, Jid.fromString(intent.getExtras().getString("contact"))); + if (this.mConversation == null) { + return false; + } + } catch (final InvalidJidException ignored) { + return false; + } + this.mode = intent.getIntExtra("mode", MODE_MANUAL_VERIFICATION); + if (this.mode == MODE_SCAN_FINGERPRINT) { + new IntentIntegrator(this).initiateScan(); + return false; + } + return true; + } else { + return false; + } + } - @Override - public void onActivityResult(int requestCode, int resultCode, Intent intent) { - if ((requestCode & 0xFFFF) == IntentIntegrator.REQUEST_CODE) { - IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent); - if (scanResult != null && scanResult.getFormatName() != null) { - String data = scanResult.getContents(); - XmppUri uri = new XmppUri(data); - if (xmppConnectionServiceBound) { - verifyWithUri(uri); - finish(); - } else { - this.mPendingUri = uri; - } - } else { - finish(); - } - } - super.onActivityResult(requestCode, requestCode, intent); - } + @Override + public void onActivityResult(int requestCode, int resultCode, Intent intent) { + if ((requestCode & 0xFFFF) == IntentIntegrator.REQUEST_CODE) { + IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent); + if (scanResult != null && scanResult.getFormatName() != null) { + String data = scanResult.getContents(); + XmppUri uri = new XmppUri(data); + if (xmppConnectionServiceBound) { + verifyWithUri(uri); + finish(); + } else { + this.mPendingUri = uri; + } + } else { + finish(); + } + } + super.onActivityResult(requestCode, requestCode, intent); + } - @Override - protected void onBackendConnected() { - if (handleIntent(getIntent())) { - updateView(); - } else if (mPendingUri!=null) { - verifyWithUri(mPendingUri); - finish(); - mPendingUri = null; - } - setIntent(null); - } + @Override + protected void onBackendConnected() { + if (handleIntent(getIntent())) { + updateView(); + } else if (mPendingUri != null) { + verifyWithUri(mPendingUri); + finish(); + mPendingUri = null; + } + setIntent(null); + } - protected void updateView() { - if (this.mConversation != null && this.mConversation.hasValidOtrSession()) { - final ActionBar actionBar = getActionBar(); - this.mVerificationExplain.setText(R.string.no_otr_session_found); - invalidateOptionsMenu(); - switch(this.mode) { - case MODE_ASK_QUESTION: - if (actionBar != null ) { - actionBar.setTitle(R.string.ask_question); - } - this.updateViewAskQuestion(); - break; - case MODE_ANSWER_QUESTION: - if (actionBar != null ) { - actionBar.setTitle(R.string.smp_requested); - } - this.updateViewAnswerQuestion(); - break; - case MODE_MANUAL_VERIFICATION: - default: - if (actionBar != null ) { - actionBar.setTitle(R.string.manually_verify); - } - this.updateViewManualVerification(); - break; - } - } else { - this.mManualVerificationArea.setVisibility(View.GONE); - this.mSmpVerificationArea.setVisibility(View.GONE); - } - } + protected void updateView() { + if (this.mConversation != null && this.mConversation.hasValidOtrSession()) { + final ActionBar actionBar = getActionBar(); + this.mVerificationExplain.setText(R.string.no_otr_session_found); + invalidateOptionsMenu(); + switch (this.mode) { + case MODE_ASK_QUESTION: + if (actionBar != null) { + actionBar.setTitle(R.string.ask_question); + } + this.updateViewAskQuestion(); + break; + case MODE_ANSWER_QUESTION: + if (actionBar != null) { + actionBar.setTitle(R.string.smp_requested); + } + this.updateViewAnswerQuestion(); + break; + case MODE_MANUAL_VERIFICATION: + default: + if (actionBar != null) { + actionBar.setTitle(R.string.manually_verify); + } + this.updateViewManualVerification(); + break; + } + } else { + this.mManualVerificationArea.setVisibility(View.GONE); + this.mSmpVerificationArea.setVisibility(View.GONE); + } + } - protected void updateViewManualVerification() { - this.mVerificationExplain.setText(R.string.manual_verification_explanation); - this.mManualVerificationArea.setVisibility(View.VISIBLE); - this.mSmpVerificationArea.setVisibility(View.GONE); - this.mYourFingerprint.setText(CryptoHelper.prettifyFingerprint(this.mAccount.getOtrFingerprint())); - this.mRemoteFingerprint.setText(CryptoHelper.prettifyFingerprint(this.mConversation.getOtrFingerprint())); - if (this.mConversation.isOtrFingerprintVerified()) { - deactivateButton(this.mRightButton,R.string.verified); - activateButton(this.mLeftButton,R.string.cancel,this.mFinishListener); - } else { - activateButton(this.mLeftButton,R.string.cancel,this.mFinishListener); - activateButton(this.mRightButton,R.string.verify, new View.OnClickListener() { - @Override - public void onClick(View view) { - showManuallyVerifyDialog(); - } - }); - } - } + protected void updateViewManualVerification() { + this.mVerificationExplain.setText(R.string.manual_verification_explanation); + this.mManualVerificationArea.setVisibility(View.VISIBLE); + this.mSmpVerificationArea.setVisibility(View.GONE); + this.mYourFingerprint.setText(CryptoHelper.prettifyFingerprint(this.mAccount.getOtrFingerprint())); + this.mRemoteFingerprint.setText(CryptoHelper.prettifyFingerprint(this.mConversation.getOtrFingerprint())); + if (this.mConversation.isOtrFingerprintVerified()) { + deactivateButton(this.mRightButton, R.string.verified); + activateButton(this.mLeftButton, R.string.cancel, this.mFinishListener); + } else { + activateButton(this.mLeftButton, R.string.cancel, this.mFinishListener); + activateButton(this.mRightButton, R.string.verify, new View.OnClickListener() { + @Override + public void onClick(View view) { + showManuallyVerifyDialog(); + } + }); + } + } - protected void updateViewAskQuestion() { - this.mManualVerificationArea.setVisibility(View.GONE); - this.mSmpVerificationArea.setVisibility(View.VISIBLE); - this.mVerificationExplain.setText(R.string.smp_explain_question); - final int smpStatus = this.mConversation.smp().status; - switch (smpStatus) { - case Conversation.Smp.STATUS_WE_REQUESTED: - this.mStatusMessage.setVisibility(View.GONE); - this.mSharedSecretHintEditable.setVisibility(View.VISIBLE); - this.mSharedSecretSecret.setVisibility(View.VISIBLE); - this.mSharedSecretHintEditable.setText(this.mConversation.smp().hint); - this.mSharedSecretSecret.setText(this.mConversation.smp().secret); - this.activateButton(this.mLeftButton, R.string.cancel, this.mCancelSharedSecretListener); - this.deactivateButton(this.mRightButton, R.string.in_progress); - break; - case Conversation.Smp.STATUS_FAILED: - this.mStatusMessage.setVisibility(View.GONE); - this.mSharedSecretHintEditable.setVisibility(View.VISIBLE); - this.mSharedSecretSecret.setVisibility(View.VISIBLE); - this.mSharedSecretSecret.requestFocus(); - this.mSharedSecretSecret.setError(getString(R.string.secrets_do_not_match)); - this.deactivateButton(this.mLeftButton, R.string.cancel); - this.activateButton(this.mRightButton, R.string.try_again, this.mRetrySharedSecretListener); - break; - case Conversation.Smp.STATUS_VERIFIED: - this.mSharedSecretHintEditable.setText(""); - this.mSharedSecretHintEditable.setVisibility(View.GONE); - this.mSharedSecretSecret.setText(""); - this.mSharedSecretSecret.setVisibility(View.GONE); - this.mStatusMessage.setVisibility(View.VISIBLE); - this.deactivateButton(this.mLeftButton, R.string.cancel); - this.activateButton(this.mRightButton, R.string.finish, this.mFinishListener); - break; - default: - this.mStatusMessage.setVisibility(View.GONE); - this.mSharedSecretHintEditable.setVisibility(View.VISIBLE); - this.mSharedSecretSecret.setVisibility(View.VISIBLE); - this.activateButton(this.mLeftButton,R.string.cancel,this.mFinishListener); - this.activateButton(this.mRightButton, R.string.ask_question, this.mCreateSharedSecretListener); - break; - } - } + protected void updateViewAskQuestion() { + this.mManualVerificationArea.setVisibility(View.GONE); + this.mSmpVerificationArea.setVisibility(View.VISIBLE); + this.mVerificationExplain.setText(R.string.smp_explain_question); + final int smpStatus = this.mConversation.smp().status; + switch (smpStatus) { + case Conversation.Smp.STATUS_WE_REQUESTED: + this.mStatusMessage.setVisibility(View.GONE); + this.mSharedSecretHintEditable.setVisibility(View.VISIBLE); + this.mSharedSecretSecret.setVisibility(View.VISIBLE); + this.mSharedSecretHintEditable.setText(this.mConversation.smp().hint); + this.mSharedSecretSecret.setText(this.mConversation.smp().secret); + this.activateButton(this.mLeftButton, R.string.cancel, this.mCancelSharedSecretListener); + this.deactivateButton(this.mRightButton, R.string.in_progress); + break; + case Conversation.Smp.STATUS_FAILED: + this.mStatusMessage.setVisibility(View.GONE); + this.mSharedSecretHintEditable.setVisibility(View.VISIBLE); + this.mSharedSecretSecret.setVisibility(View.VISIBLE); + this.mSharedSecretSecret.requestFocus(); + this.mSharedSecretSecret.setError(getString(R.string.secrets_do_not_match)); + this.deactivateButton(this.mLeftButton, R.string.cancel); + this.activateButton(this.mRightButton, R.string.try_again, this.mRetrySharedSecretListener); + break; + case Conversation.Smp.STATUS_VERIFIED: + this.mSharedSecretHintEditable.setText(""); + this.mSharedSecretHintEditable.setVisibility(View.GONE); + this.mSharedSecretSecret.setText(""); + this.mSharedSecretSecret.setVisibility(View.GONE); + this.mStatusMessage.setVisibility(View.VISIBLE); + this.deactivateButton(this.mLeftButton, R.string.cancel); + this.activateButton(this.mRightButton, R.string.finish, this.mFinishListener); + break; + default: + this.mStatusMessage.setVisibility(View.GONE); + this.mSharedSecretHintEditable.setVisibility(View.VISIBLE); + this.mSharedSecretSecret.setVisibility(View.VISIBLE); + this.activateButton(this.mLeftButton, R.string.cancel, this.mFinishListener); + this.activateButton(this.mRightButton, R.string.ask_question, this.mCreateSharedSecretListener); + break; + } + } - protected void updateViewAnswerQuestion() { - this.mManualVerificationArea.setVisibility(View.GONE); - this.mSmpVerificationArea.setVisibility(View.VISIBLE); - this.mVerificationExplain.setText(R.string.smp_explain_answer); - this.mSharedSecretHintEditable.setVisibility(View.GONE); - this.mSharedSecretHint.setVisibility(View.VISIBLE); - this.deactivateButton(this.mLeftButton, R.string.cancel); - final int smpStatus = this.mConversation.smp().status; - switch (smpStatus) { - case Conversation.Smp.STATUS_CONTACT_REQUESTED: - this.mStatusMessage.setVisibility(View.GONE); - this.mSharedSecretHint.setText(this.mConversation.smp().hint); - this.activateButton(this.mRightButton,R.string.respond,this.mRespondSharedSecretListener); - break; - case Conversation.Smp.STATUS_VERIFIED: - this.mSharedSecretHintEditable.setText(""); - this.mSharedSecretHintEditable.setVisibility(View.GONE); - this.mSharedSecretHint.setVisibility(View.GONE); - this.mSharedSecretSecret.setText(""); - this.mSharedSecretSecret.setVisibility(View.GONE); - this.mStatusMessage.setVisibility(View.VISIBLE); - this.activateButton(this.mRightButton, R.string.finish, this.mFinishListener); - break; - case Conversation.Smp.STATUS_FAILED: - default: - this.mSharedSecretSecret.requestFocus(); - this.mSharedSecretSecret.setError(getString(R.string.secrets_do_not_match)); - this.activateButton(this.mRightButton,R.string.finish,this.mFinishListener); - break; - } - } + protected void updateViewAnswerQuestion() { + this.mManualVerificationArea.setVisibility(View.GONE); + this.mSmpVerificationArea.setVisibility(View.VISIBLE); + this.mVerificationExplain.setText(R.string.smp_explain_answer); + this.mSharedSecretHintEditable.setVisibility(View.GONE); + this.mSharedSecretHint.setVisibility(View.VISIBLE); + this.deactivateButton(this.mLeftButton, R.string.cancel); + final int smpStatus = this.mConversation.smp().status; + switch (smpStatus) { + case Conversation.Smp.STATUS_CONTACT_REQUESTED: + this.mStatusMessage.setVisibility(View.GONE); + this.mSharedSecretHint.setText(this.mConversation.smp().hint); + this.activateButton(this.mRightButton, R.string.respond, this.mRespondSharedSecretListener); + break; + case Conversation.Smp.STATUS_VERIFIED: + this.mSharedSecretHintEditable.setText(""); + this.mSharedSecretHintEditable.setVisibility(View.GONE); + this.mSharedSecretHint.setVisibility(View.GONE); + this.mSharedSecretSecret.setText(""); + this.mSharedSecretSecret.setVisibility(View.GONE); + this.mStatusMessage.setVisibility(View.VISIBLE); + this.activateButton(this.mRightButton, R.string.finish, this.mFinishListener); + break; + case Conversation.Smp.STATUS_FAILED: + default: + this.mSharedSecretSecret.requestFocus(); + this.mSharedSecretSecret.setError(getString(R.string.secrets_do_not_match)); + this.activateButton(this.mRightButton, R.string.finish, this.mFinishListener); + break; + } + } - protected void activateButton(Button button, int text, View.OnClickListener listener) { - button.setEnabled(true); - button.setTextColor(getPrimaryTextColor()); - button.setText(text); - button.setOnClickListener(listener); - } + protected void activateButton(Button button, int text, View.OnClickListener listener) { + button.setEnabled(true); + button.setTextColor(getPrimaryTextColor()); + button.setText(text); + button.setOnClickListener(listener); + } - protected void deactivateButton(Button button, int text) { - button.setEnabled(false); - button.setTextColor(getSecondaryTextColor()); - button.setText(text); - button.setOnClickListener(null); - } + protected void deactivateButton(Button button, int text) { + button.setEnabled(false); + button.setTextColor(getSecondaryTextColor()); + button.setText(text); + button.setOnClickListener(null); + } - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_verify_otr); - this.mRemoteFingerprint = (TextView) findViewById(R.id.remote_fingerprint); - this.mYourFingerprint = (TextView) findViewById(R.id.your_fingerprint); - this.mLeftButton = (Button) findViewById(R.id.left_button); - this.mRightButton = (Button) findViewById(R.id.right_button); - this.mVerificationExplain = (TextView) findViewById(R.id.verification_explanation); - this.mStatusMessage = (TextView) findViewById(R.id.status_message); - this.mSharedSecretSecret = (EditText) findViewById(R.id.shared_secret_secret); - this.mSharedSecretHintEditable = (EditText) findViewById(R.id.shared_secret_hint_editable); - this.mSharedSecretHint = (TextView) findViewById(R.id.shared_secret_hint); - this.mManualVerificationArea = (LinearLayout) findViewById(R.id.manual_verification_area); - this.mSmpVerificationArea = (LinearLayout) findViewById(R.id.smp_verification_area); - } + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_verify_otr); + this.mRemoteFingerprint = (TextView) findViewById(R.id.remote_fingerprint); + this.mYourFingerprint = (TextView) findViewById(R.id.your_fingerprint); + this.mLeftButton = (Button) findViewById(R.id.left_button); + this.mRightButton = (Button) findViewById(R.id.right_button); + this.mVerificationExplain = (TextView) findViewById(R.id.verification_explanation); + this.mStatusMessage = (TextView) findViewById(R.id.status_message); + this.mSharedSecretSecret = (EditText) findViewById(R.id.shared_secret_secret); + this.mSharedSecretHintEditable = (EditText) findViewById(R.id.shared_secret_hint_editable); + this.mSharedSecretHint = (TextView) findViewById(R.id.shared_secret_hint); + this.mManualVerificationArea = (LinearLayout) findViewById(R.id.manual_verification_area); + this.mSmpVerificationArea = (LinearLayout) findViewById(R.id.smp_verification_area); + } - @Override - public boolean onCreateOptionsMenu(final Menu menu) { - super.onCreateOptionsMenu(menu); - getMenuInflater().inflate(R.menu.verify_otr, menu); - return true; - } + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + super.onCreateOptionsMenu(menu); + getMenuInflater().inflate(R.menu.verify_otr, menu); + return true; + } - private void showManuallyVerifyDialog() { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.manually_verify); - builder.setMessage(R.string.are_you_sure_verify_fingerprint); - builder.setNegativeButton(R.string.cancel, null); - builder.setPositiveButton(R.string.verify, mVerifyFingerprintListener); - builder.create().show(); - } + private void showManuallyVerifyDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.manually_verify); + builder.setMessage(R.string.are_you_sure_verify_fingerprint); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(R.string.verify, mVerifyFingerprintListener); + builder.create().show(); + } - @Override - protected String getShareableUri() { - if (mAccount!=null) { - return mAccount.getShareableUri(); - } else { - return ""; - } - } + @Override + protected String getShareableUri() { + if (mAccount != null) { + return mAccount.getShareableUri(); + } else { + return ""; + } + } - public void onConversationUpdate() { - refreshUi(); - } + public void onConversationUpdate() { + refreshUi(); + } - @Override - protected void refreshUiReal() { - updateView(); - } + @Override + protected void refreshUiReal() { + updateView(); + } } diff --git a/src/main/java/de/pixart/messenger/ui/WelcomeActivity.java b/src/main/java/de/pixart/messenger/ui/WelcomeActivity.java index f0235ce97..7573e40c7 100644 --- a/src/main/java/de/pixart/messenger/ui/WelcomeActivity.java +++ b/src/main/java/de/pixart/messenger/ui/WelcomeActivity.java @@ -101,8 +101,8 @@ public class WelcomeActivity extends XmppActivity { List accounts = xmppConnectionService.getAccounts(); Intent intent = new Intent(WelcomeActivity.this, EditAccountActivity.class); if (accounts.size() == 1) { - intent.putExtra("jid",accounts.get(0).getJid().toBareJid().toString()); - intent.putExtra("init",true); + intent.putExtra("jid", accounts.get(0).getJid().toBareJid().toString()); + intent.putExtra("init", true); } else if (accounts.size() >= 1) { intent = new Intent(WelcomeActivity.this, ManageAccountActivity.class); } @@ -125,7 +125,7 @@ public class WelcomeActivity extends XmppActivity { .setCancelable(false) .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog,int id) { + public void onClick(DialogInterface dialog, int id) { final String password = userInput.getText().toString(); final ProgressDialog pd = ProgressDialog.show(WelcomeActivity.this, getString(R.string.please_wait), getString(R.string.databaseimport_started), true); if (!password.isEmpty()) { @@ -157,7 +157,7 @@ public class WelcomeActivity extends XmppActivity { }) .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog,int id) { + public void onClick(DialogInterface dialog, int id) { Toast.makeText(WelcomeActivity.this, R.string.import_canceled, Toast.LENGTH_LONG).show(); dialog.dismiss(); } @@ -176,12 +176,12 @@ public class WelcomeActivity extends XmppActivity { private boolean BackupAvailable() { // Set the folder on the SDcard File filePath = new File(FileBackend.getConversationsDirectory() + "/database/database.db.crypt"); - Log.d(Config.LOGTAG,"DB Path: " + filePath.toString()); - if(filePath.exists()) { - Log.d(Config.LOGTAG,"DB Path existing"); + Log.d(Config.LOGTAG, "DB Path: " + filePath.toString()); + if (filePath.exists()) { + Log.d(Config.LOGTAG, "DB Path existing"); return true; } else { - Log.d(Config.LOGTAG,"DB Path not existing"); + Log.d(Config.LOGTAG, "DB Path not existing"); return false; } } @@ -198,16 +198,16 @@ public class WelcomeActivity extends XmppActivity { try { EncryptDecryptFile.decrypt(InputFile, OutputTemp, DecryptionKey); } catch (NoSuchAlgorithmException e) { - Log.d(Config.LOGTAG,"Database importer: decryption failed with " + e); + Log.d(Config.LOGTAG, "Database importer: decryption failed with " + e); e.printStackTrace(); } catch (NoSuchPaddingException e) { - Log.d(Config.LOGTAG,"Database importer: decryption failed with " + e); + Log.d(Config.LOGTAG, "Database importer: decryption failed with " + e); e.printStackTrace(); } catch (InvalidKeyException e) { - Log.d(Config.LOGTAG,"Database importer: decryption failed (invalid key) with " + e); + Log.d(Config.LOGTAG, "Database importer: decryption failed (invalid key) with " + e); e.printStackTrace(); } catch (IOException e) { - Log.d(Config.LOGTAG,"Database importer: decryption failed (IO) with " + e); + Log.d(Config.LOGTAG, "Database importer: decryption failed (IO) with " + e); e.printStackTrace(); } diff --git a/src/main/java/de/pixart/messenger/ui/XmppActivity.java b/src/main/java/de/pixart/messenger/ui/XmppActivity.java index 26f185294..8b3d26179 100644 --- a/src/main/java/de/pixart/messenger/ui/XmppActivity.java +++ b/src/main/java/de/pixart/messenger/ui/XmppActivity.java @@ -92,1209 +92,1209 @@ import de.pixart.messenger.xmpp.jid.Jid; public abstract class XmppActivity extends Activity { - protected static final int REQUEST_ANNOUNCE_PGP = 0x0101; - protected static final int REQUEST_INVITE_TO_CONVERSATION = 0x0102; - protected static final int REQUEST_CHOOSE_PGP_ID = 0x0103; - protected static final int REQUEST_BATTERY_OP = 0x13849ff; - - public static final String EXTRA_ACCOUNT = "account"; - - public XmppConnectionService xmppConnectionService; - public boolean xmppConnectionServiceBound = false; - protected boolean registeredListeners = false; - - protected int mPrimaryTextColor; - protected int mSecondaryTextColor; - protected int mTertiaryTextColor; - protected int mPrimaryBackgroundColor; - protected int mSecondaryBackgroundColor; - protected int mColorRed; - protected int mColorWhite; - protected int mColorOrange; - protected int mColorGreen; - protected int mPrimaryColor; - - protected boolean mUseSubject = true; - - private DisplayMetrics metrics; - protected int mTheme; - protected boolean mUsingEnterKey = false; - - protected Toast mToast; - - protected void hideToast() { - if (mToast != null) { - mToast.cancel(); - } - } - - protected void replaceToast(String msg) { - replaceToast(msg, true); - } - - protected void replaceToast(String msg, boolean showlong) { - hideToast(); - mToast = Toast.makeText(this, msg ,showlong ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT); - mToast.show(); - } - - public void showProgress() { - - } - - public void closeProgress() { - - } - - protected Runnable onOpenPGPKeyPublished = new Runnable() { - @Override - public void run() { - Toast.makeText(XmppActivity.this,R.string.openpgp_has_been_published, Toast.LENGTH_SHORT).show(); - } - }; - - private long mLastUiRefresh = 0; - private Handler mRefreshUiHandler = new Handler(); - private Runnable mRefreshUiRunnable = new Runnable() { - @Override - public void run() { - mLastUiRefresh = SystemClock.elapsedRealtime(); - refreshUiReal(); - } - }; - - protected ConferenceInvite mPendingConferenceInvite = null; - - - protected final void refreshUi() { - final long diff = SystemClock.elapsedRealtime() - mLastUiRefresh; - if (diff > Config.REFRESH_UI_INTERVAL) { - mRefreshUiHandler.removeCallbacks(mRefreshUiRunnable); - runOnUiThread(mRefreshUiRunnable); - } else { - final long next = Config.REFRESH_UI_INTERVAL - diff; - mRefreshUiHandler.removeCallbacks(mRefreshUiRunnable); - mRefreshUiHandler.postDelayed(mRefreshUiRunnable,next); - } - } - - abstract protected void refreshUiReal(); - - protected interface OnValueEdited { - public void onValueEdited(String value); - } - - public interface OnPresenceSelected { - public void onPresenceSelected(); - } - - protected ServiceConnection mConnection = new ServiceConnection() { - - @Override - public void onServiceConnected(ComponentName className, IBinder service) { - XmppConnectionBinder binder = (XmppConnectionBinder) service; - xmppConnectionService = binder.getService(); - xmppConnectionServiceBound = true; - if (!registeredListeners && shouldRegisterListeners()) { - registerListeners(); - registeredListeners = true; - } - onBackendConnected(); - } - - @Override - public void onServiceDisconnected(ComponentName arg0) { - xmppConnectionServiceBound = false; - } - }; - - @Override - protected void onStart() { - super.onStart(); - if (!xmppConnectionServiceBound) { - connectToBackend(); - } else { - if (!registeredListeners) { - this.registerListeners(); - this.registeredListeners = true; - } - this.onBackendConnected(); - } - } - - @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) - protected boolean shouldRegisterListeners() { - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - return !isDestroyed() && !isFinishing(); - } else { - return !isFinishing(); - } - } - - public void connectToBackend() { - Intent intent = new Intent(this, XmppConnectionService.class); - intent.setAction("ui"); - startService(intent); - bindService(intent, mConnection, Context.BIND_AUTO_CREATE); - } - - @Override - protected void onStop() { - super.onStop(); - if (xmppConnectionServiceBound) { - if (registeredListeners) { - this.unregisterListeners(); - this.registeredListeners = false; - } - unbindService(mConnection); - xmppConnectionServiceBound = false; - } - } - - protected void hideKeyboard() { - InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - - View focus = getCurrentFocus(); - - if (focus != null) { - - inputManager.hideSoftInputFromWindow(focus.getWindowToken(), - InputMethodManager.HIDE_NOT_ALWAYS); - } - } - - public boolean hasPgp() { - return xmppConnectionService.getPgpEngine() != null; - } - - public void showInstallPgpDialog() { - Builder builder = new AlertDialog.Builder(this); - builder.setTitle(getString(R.string.openkeychain_required)); - builder.setIconAttribute(android.R.attr.alertDialogIcon); - builder.setMessage(getText(R.string.openkeychain_required_long)); - builder.setNegativeButton(getString(R.string.cancel), null); - builder.setNeutralButton(getString(R.string.restart), - new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - if (xmppConnectionServiceBound) { - unbindService(mConnection); - xmppConnectionServiceBound = false; - } - stopService(new Intent(XmppActivity.this, - XmppConnectionService.class)); - finish(); - } - }); - builder.setPositiveButton(getString(R.string.install), - new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - Uri uri = Uri - .parse("market://details?id=org.sufficientlysecure.keychain"); - Intent marketIntent = new Intent(Intent.ACTION_VIEW, - uri); - PackageManager manager = getApplicationContext() - .getPackageManager(); - List infos = manager - .queryIntentActivities(marketIntent, 0); - if (infos.size() > 0) { - startActivity(marketIntent); - } else { - uri = Uri.parse("http://www.openkeychain.org/"); - Intent browserIntent = new Intent( - Intent.ACTION_VIEW, uri); - startActivity(browserIntent); - } - finish(); - } - }); - builder.create().show(); - } - - abstract void onBackendConnected(); - - protected void registerListeners() { - if (this instanceof XmppConnectionService.OnConversationUpdate) { - this.xmppConnectionService.setOnConversationListChangedListener((XmppConnectionService.OnConversationUpdate) this); - } - if (this instanceof XmppConnectionService.OnAccountUpdate) { - this.xmppConnectionService.setOnAccountListChangedListener((XmppConnectionService.OnAccountUpdate) this); - } - if (this instanceof XmppConnectionService.OnCaptchaRequested) { - this.xmppConnectionService.setOnCaptchaRequestedListener((XmppConnectionService.OnCaptchaRequested) this); - } - if (this instanceof XmppConnectionService.OnRosterUpdate) { - this.xmppConnectionService.setOnRosterUpdateListener((XmppConnectionService.OnRosterUpdate) this); - } - if (this instanceof XmppConnectionService.OnMucRosterUpdate) { - this.xmppConnectionService.setOnMucRosterUpdateListener((XmppConnectionService.OnMucRosterUpdate) this); - } - if (this instanceof OnUpdateBlocklist) { - this.xmppConnectionService.setOnUpdateBlocklistListener((OnUpdateBlocklist) this); - } - if (this instanceof XmppConnectionService.OnShowErrorToast) { - this.xmppConnectionService.setOnShowErrorToastListener((XmppConnectionService.OnShowErrorToast) this); - } - if (this instanceof OnKeyStatusUpdated) { - this.xmppConnectionService.setOnKeyStatusUpdatedListener((OnKeyStatusUpdated) this); - } - } - - protected void unregisterListeners() { - if (this instanceof XmppConnectionService.OnConversationUpdate) { - this.xmppConnectionService.removeOnConversationListChangedListener(); - } - if (this instanceof XmppConnectionService.OnAccountUpdate) { - this.xmppConnectionService.removeOnAccountListChangedListener(); - } - if (this instanceof XmppConnectionService.OnCaptchaRequested) { - this.xmppConnectionService.removeOnCaptchaRequestedListener(); - } - if (this instanceof XmppConnectionService.OnRosterUpdate) { - this.xmppConnectionService.removeOnRosterUpdateListener(); - } - if (this instanceof XmppConnectionService.OnMucRosterUpdate) { - this.xmppConnectionService.removeOnMucRosterUpdateListener(); - } - if (this instanceof OnUpdateBlocklist) { - this.xmppConnectionService.removeOnUpdateBlocklistListener(); - } - if (this instanceof XmppConnectionService.OnShowErrorToast) { - this.xmppConnectionService.removeOnShowErrorToastListener(); - } - if (this instanceof OnKeyStatusUpdated) { - this.xmppConnectionService.removeOnNewKeysAvailableListener(); - } - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case R.id.action_invite_user: - inviteUser(); - break; - case R.id.action_create_issue: - createIssue(); - break; - case R.id.action_settings: - startActivity(new Intent(this, SettingsActivity.class)); - break; - case R.id.action_check_updates: - if (xmppConnectionService.hasInternetConnection()) { - startActivity(new Intent(this, UpdaterActivity.class)); - } else { - Toast.makeText(this, R.string.account_status_no_internet, Toast.LENGTH_LONG).show(); - } - break; - case R.id.action_accounts: - final Intent intent = new Intent(getApplicationContext(), EditAccountActivity.class); - Account mAccount = xmppConnectionService.getAccounts().get(0); - intent.putExtra("jid", mAccount.getJid().toBareJid().toString()); - intent.putExtra("init", false); - startActivity(intent); - break; - case android.R.id.home: - finish(); - break; - case R.id.action_show_qr_code: - showQrCode(); - break; - } - return super.onOptionsItemSelected(item); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - metrics = getResources().getDisplayMetrics(); - ExceptionHelper.init(getApplicationContext()); - mPrimaryTextColor = getResources().getColor(R.color.black87); - mSecondaryTextColor = getResources().getColor(R.color.black54); - mTertiaryTextColor = getResources().getColor(R.color.black12); - mColorRed = getResources().getColor(R.color.red800); - mColorWhite = getResources().getColor(R.color.white70); - mColorOrange = getResources().getColor(R.color.orange500); - mColorGreen = getResources().getColor(R.color.realgreen); - mPrimaryColor = getResources().getColor(R.color.primary); - mPrimaryBackgroundColor = getResources().getColor(R.color.grey50); - mSecondaryBackgroundColor = getResources().getColor(R.color.grey200); - this.mTheme = findTheme(); - setTheme(this.mTheme); - this.mUsingEnterKey = usingEnterKey(); - mUseSubject = getPreferences().getBoolean("use_subject", true); - final ActionBar ab = getActionBar(); - if (ab!=null) { - ab.setDisplayHomeAsUpEnabled(true); - } - } - - protected boolean isOptimizingBattery() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); - return !pm.isIgnoringBatteryOptimizations(getPackageName()); - } else { - return false; - } - } - - protected boolean isAffectedByDataSaver() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); - return cm.isActiveNetworkMetered() - && cm.getRestrictBackgroundStatus() == ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED; - } else { - return false; - } - } - - 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); - } - - public void switchToConversation(Conversation conversation, String text, - boolean newTask) { - switchToConversation(conversation,text,null,false,newTask); - } - - public void highlightInMuc(Conversation conversation, String nick) { - switchToConversation(conversation, null, nick, false, false); - } - - public void privateMsgInMuc(Conversation conversation, String nick) { - switchToConversation(conversation, null, nick, true, false); - } - - private void switchToConversation(Conversation conversation, String text, String nick, boolean pm, boolean newTask) { - Intent viewConversationIntent = new Intent(this, - ConversationActivity.class); - viewConversationIntent.setAction(ConversationActivity.ACTION_VIEW_CONVERSATION); - viewConversationIntent.putExtra(ConversationActivity.CONVERSATION, - conversation.getUuid()); - if (text != null) { - viewConversationIntent.putExtra(ConversationActivity.TEXT, text); - } - if (nick != null) { - viewConversationIntent.putExtra(ConversationActivity.NICK, nick); - viewConversationIntent.putExtra(ConversationActivity.PRIVATE_MESSAGE,pm); - } - if (newTask) { - viewConversationIntent.setFlags(viewConversationIntent.getFlags() - | Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_SINGLE_TOP); - } else { - viewConversationIntent.setFlags(viewConversationIntent.getFlags() - | Intent.FLAG_ACTIVITY_CLEAR_TOP); - } - startActivity(viewConversationIntent); - finish(); - } - - public void switchToContactDetails(Contact contact) { - switchToContactDetails(contact, null); - } - - public void switchToContactDetails(Contact contact, String messageFingerprint) { - Intent intent = new Intent(this, ContactDetailsActivity.class); - intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT); - intent.putExtra(EXTRA_ACCOUNT, contact.getAccount().getJid().toBareJid().toString()); - intent.putExtra("contact", contact.getJid().toString()); - intent.putExtra("fingerprint", messageFingerprint); - startActivity(intent); - } - - public void switchToAccount(Account account) { - switchToAccount(account, false); - } - - public void switchToAccount(Account account, boolean init) { - Intent intent = new Intent(this, EditAccountActivity.class); - intent.putExtra("jid", account.getJid().toBareJid().toString()); - intent.putExtra("init", init); - startActivity(intent); - } - - protected void inviteToConversation(Conversation conversation) { - Intent intent = new Intent(getApplicationContext(), - ChooseContactActivity.class); - List contacts = new ArrayList<>(); - if (conversation.getMode() == Conversation.MODE_MULTI) { - for (MucOptions.User user : conversation.getMucOptions().getUsers(false)) { - Jid jid = user.getRealJid(); - if (jid != null) { - contacts.add(jid.toBareJid().toString()); - } - } - } else { - contacts.add(conversation.getJid().toBareJid().toString()); - } - intent.putExtra("filter_contacts", contacts.toArray(new String[contacts.size()])); - intent.putExtra("conversation", conversation.getUuid()); - intent.putExtra("multiple", true); - intent.putExtra("show_enter_jid", true); - intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().toBareJid().toString()); - startActivityForResult(intent, REQUEST_INVITE_TO_CONVERSATION); - } - - protected void announcePgp(Account account, final Conversation conversation, final Runnable onSuccess) { - if (account.getPgpId() == 0) { - choosePgpSignId(account); - } else { - String status = null; - if (manuallyChangePresence()) { - status = account.getPresenceStatusMessage(); - } - if (status == null) { - status = ""; - } - xmppConnectionService.getPgpEngine().generateSignature(account, status, new UiCallback() { - - @Override - public void userInputRequried(PendingIntent pi, Account account) { - try { - startIntentSenderForResult(pi.getIntentSender(), REQUEST_ANNOUNCE_PGP, null, 0, 0, 0); - } catch (final SendIntentException ignored) { - } - } - - @Override - public void success(Account account) { - xmppConnectionService.databaseBackend.updateAccount(account); - xmppConnectionService.sendPresence(account); - if (conversation != null) { - conversation.setNextEncryption(Message.ENCRYPTION_PGP); - xmppConnectionService.updateConversation(conversation); - refreshUi(); - } - if (onSuccess != null) { - runOnUiThread(onSuccess); - } - } - - @Override - public void error(int error, Account account) { - if (error == 0 && account != null) { - account.setPgpSignId(0); - account.unsetPgpSignature(); - xmppConnectionService.databaseBackend.updateAccount(account); - choosePgpSignId(account); - } else { - displayErrorDialog(error); - } - } - }); - } - } - - protected boolean noAccountUsesPgp() { - if (!hasPgp()) { - return true; - } - for(Account account : xmppConnectionService.getAccounts()) { - if (account.getPgpId() != 0) { - return false; - } - } - return true; - } - - @SuppressWarnings("deprecation") - @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - protected void setListItemBackgroundOnView(View view) { - int sdk = android.os.Build.VERSION.SDK_INT; - if (sdk < android.os.Build.VERSION_CODES.JELLY_BEAN) { - view.setBackgroundDrawable(getResources().getDrawable(R.drawable.greybackground)); - } else { - view.setBackground(getResources().getDrawable(R.drawable.greybackground)); - } - } - - protected void choosePgpSignId(Account account) { - xmppConnectionService.getPgpEngine().chooseKey(account, new UiCallback() { - @Override - public void success(Account account1) { - } - - @Override - public void error(int errorCode, Account object) { - - } - - @Override - public void userInputRequried(PendingIntent pi, Account object) { - try { - startIntentSenderForResult(pi.getIntentSender(), - REQUEST_CHOOSE_PGP_ID, null, 0, 0, 0); - } catch (final SendIntentException ignored) { - } - } - }); - } - - protected void displayErrorDialog(final int errorCode) { - runOnUiThread(new Runnable() { - - @Override - public void run() { - AlertDialog.Builder builder = new AlertDialog.Builder( - XmppActivity.this); - builder.setIconAttribute(android.R.attr.alertDialogIcon); - builder.setTitle(getString(R.string.error)); - builder.setMessage(errorCode); - builder.setNeutralButton(R.string.accept, null); - builder.create().show(); - } - }); - - } - - protected void showAddToRosterDialog(final Conversation conversation) { - showAddToRosterDialog(conversation.getContact()); - } - - protected void showAddToRosterDialog(final Contact contact) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(contact.getJid().toString()); - builder.setMessage(getString(R.string.not_in_roster)); - builder.setNegativeButton(getString(R.string.cancel), null); - builder.setPositiveButton(getString(R.string.add_contact), - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - final Jid jid = contact.getJid(); - Account account = contact.getAccount(); - Contact contact = account.getRoster().getContact(jid); - xmppConnectionService.createContact(contact); - } - }); - builder.create().show(); - } - - private void showAskForPresenceDialog(final Contact contact) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(contact.getJid().toString()); - builder.setMessage(R.string.request_presence_updates); - builder.setNegativeButton(R.string.cancel, null); - builder.setPositiveButton(R.string.request_now, - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - if (xmppConnectionServiceBound) { - xmppConnectionService.sendPresencePacket(contact - .getAccount(), xmppConnectionService - .getPresenceGenerator() - .requestPresenceUpdatesFrom(contact)); - } - } - }); - builder.create().show(); - } - - private void warnMutalPresenceSubscription(final Conversation conversation, - final OnPresenceSelected listener) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(conversation.getContact().getJid().toString()); - builder.setMessage(R.string.without_mutual_presence_updates); - builder.setNegativeButton(R.string.cancel, null); - builder.setPositiveButton(R.string.ignore, new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - conversation.setNextCounterpart(null); - if (listener != null) { - listener.onPresenceSelected(); - } - } - }); - builder.create().show(); - } - - protected void quickEdit(String previousValue, int hint, OnValueEdited callback) { - quickEdit(previousValue, callback, hint, false); - } - - protected void quickPasswordEdit(String previousValue, OnValueEdited callback) { - quickEdit(previousValue, callback, R.string.password, true); - } - - @SuppressLint("InflateParams") - private void quickEdit(final String previousValue, - final OnValueEdited callback, - final int hint, - boolean password) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - View view = getLayoutInflater().inflate(R.layout.quickedit, null); - final EditText editor = (EditText) view.findViewById(R.id.editor); - OnClickListener mClickListener = new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - String value = editor.getText().toString(); - if (!value.equals(previousValue) && value.trim().length() > 0) { - callback.onValueEdited(value); - } - } - }; - if (password) { - editor.setInputType(InputType.TYPE_CLASS_TEXT - | InputType.TYPE_TEXT_VARIATION_PASSWORD); - builder.setPositiveButton(R.string.accept, mClickListener); - } else { - builder.setPositiveButton(R.string.edit, mClickListener); - } - if (hint != 0) { - editor.setHint(hint); - } - editor.requestFocus(); - editor.setText(""); - if (previousValue != null) { - editor.getText().append(previousValue); - } - builder.setView(view); - builder.setNegativeButton(R.string.cancel, null); - builder.create().show(); - } - - public boolean hasStoragePermission(int requestCode) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { - requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestCode); - return false; - } else { - return true; - } - } else { - return true; - } - } - - public boolean hasMicPermission(int requestCode) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { - requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, requestCode); - return false; - } else { - return true; - } - } else { - return true; - } - } - - public boolean hasLocationPermission(int requestCode) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED || checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { - requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, requestCode); - requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, requestCode); - return false; - } else { - return true; - } - } else { - return true; - } - } - - public void selectPresence(final Conversation conversation, - final OnPresenceSelected listener) { - final Contact contact = conversation.getContact(); - if (conversation.hasValidOtrSession()) { - SessionID id = conversation.getOtrSession().getSessionID(); - Jid jid; - try { - jid = Jid.fromString(id.getAccountID() + "/" + id.getUserID()); - } catch (InvalidJidException e) { - jid = null; - } - conversation.setNextCounterpart(jid); - listener.onPresenceSelected(); - } else if (!contact.showInRoster()) { - showAddToRosterDialog(conversation); - } else { - final Presences presences = contact.getPresences(); - if (presences.size() == 0) { - if (!contact.getOption(Contact.Options.TO) - && !contact.getOption(Contact.Options.ASKING) - && contact.getAccount().getStatus() == Account.State.ONLINE) { - showAskForPresenceDialog(contact); - } else if (!contact.getOption(Contact.Options.TO) - || !contact.getOption(Contact.Options.FROM)) { - warnMutalPresenceSubscription(conversation, listener); - } else { - conversation.setNextCounterpart(null); - listener.onPresenceSelected(); - } - } else if (presences.size() == 1) { - String presence = presences.toResourceArray()[0]; - try { - conversation.setNextCounterpart(Jid.fromParts(contact.getJid().getLocalpart(),contact.getJid().getDomainpart(),presence)); - } catch (InvalidJidException e) { - conversation.setNextCounterpart(null); - } - listener.onPresenceSelected(); - } else { - showPresenceSelectionDialog(presences,conversation,listener); - } - } - } - - private void showPresenceSelectionDialog(Presences presences, final Conversation conversation, final OnPresenceSelected listener) { - final Contact contact = conversation.getContact(); - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(getString(R.string.choose_presence)); - final String[] resourceArray = presences.toResourceArray(); - Pair, Map> typeAndName = presences.toTypeAndNameMap(); - final Map resourceTypeMap = typeAndName.first; - final Map resourceNameMap = typeAndName.second; - final String[] readableIdentities = new String[resourceArray.length]; - final AtomicInteger selectedResource = new AtomicInteger(0); - for (int i = 0; i < resourceArray.length; ++i) { - String resource = resourceArray[i]; - if (resource.equals(contact.getLastResource())) { - selectedResource.set(i); - } - String type = resourceTypeMap.get(resource); - String name = resourceNameMap.get(resource); - if (type != null) { - if (Collections.frequency(resourceTypeMap.values(),type) == 1) { - readableIdentities[i] = UIHelper.tranlasteType(this,type); - } else if (name != null) { - if (Collections.frequency(resourceNameMap.values(), name) == 1 - || CryptoHelper.UUID_PATTERN.matcher(resource).matches()) { - readableIdentities[i] = UIHelper.tranlasteType(this,type) + " (" + name+")"; - } else { - readableIdentities[i] = UIHelper.tranlasteType(this,type) + " (" + name +" / " + resource+")"; - } - } else { - readableIdentities[i] = UIHelper.tranlasteType(this,type) + " (" + resource+")"; - } - } else { - readableIdentities[i] = resource; - } - } - builder.setSingleChoiceItems(readableIdentities, - selectedResource.get(), - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - selectedResource.set(which); - } - }); - builder.setNegativeButton(R.string.cancel, null); - builder.setPositiveButton(R.string.ok, new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - try { - Jid next = Jid.fromParts(contact.getJid().getLocalpart(),contact.getJid().getDomainpart(),resourceArray[selectedResource.get()]); - conversation.setNextCounterpart(next); - } catch (InvalidJidException e) { - conversation.setNextCounterpart(null); - } - listener.onPresenceSelected(); - } - }); - builder.create().show(); - } - - protected void onActivityResult(int requestCode, int resultCode, final Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (requestCode == REQUEST_INVITE_TO_CONVERSATION && resultCode == RESULT_OK) { - mPendingConferenceInvite = ConferenceInvite.parse(data); - if (xmppConnectionServiceBound && mPendingConferenceInvite != null) { - if (mPendingConferenceInvite.execute(this)) { - mToast = Toast.makeText(this, R.string.creating_conference, Toast.LENGTH_LONG); - mToast.show(); - } - mPendingConferenceInvite = null; - } - } - } - - - private UiCallback adhocCallback = new UiCallback() { - @Override - public void success(final Conversation conversation) { - runOnUiThread(new Runnable() { - @Override - public void run() { - switchToConversation(conversation); - hideToast(); - } - }); - } - - @Override - public void error(final int errorCode, Conversation object) { - runOnUiThread(new Runnable() { - @Override - public void run() { - replaceToast(getString(errorCode)); - } - }); - } - - @Override - public void userInputRequried(PendingIntent pi, Conversation object) { - - } - }; - - public int getTertiaryTextColor() { - return this.mTertiaryTextColor; - } - - public int getSecondaryTextColor() { - return this.mSecondaryTextColor; - } - - public int getPrimaryTextColor() { - return this.mPrimaryTextColor; - } - - public int getWarningTextColor() { - return this.mColorRed; - } - - public int getUnencryptedTextColor() { - return this.mColorWhite; - } - - public int getOnlineColor() { - return this.mColorGreen; - } - - public int getPrimaryBackgroundColor() { - return this.mPrimaryBackgroundColor; - } - - public int getSecondaryBackgroundColor() { - return this.mSecondaryBackgroundColor; - } - - public int getPixel(int dp) { - DisplayMetrics metrics = getResources().getDisplayMetrics(); - return ((int) (dp * metrics.density)); - } - - public boolean copyTextToClipboard(String text, int labelResId) { - ClipboardManager mClipBoardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); - String label = getResources().getString(labelResId); - if (mClipBoardManager != null) { - ClipData mClipData = ClipData.newPlainText(label, text); - mClipBoardManager.setPrimaryClip(mClipData); - return true; - } - return false; - } - - protected void registerNdefPushMessageCallback() { - NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this); - if (nfcAdapter != null && nfcAdapter.isEnabled()) { - nfcAdapter.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() { - @Override - public NdefMessage createNdefMessage(NfcEvent nfcEvent) { - return new NdefMessage(new NdefRecord[]{ - NdefRecord.createUri(getShareableUri()), - NdefRecord.createApplicationRecord("de.pixart.messenger") - }); - } - }, this); - } - } - - protected boolean neverCompressPictures() { - return getPreferences().getString("picture_compression", "auto").equals("never"); - } - - protected boolean manuallyChangePresence() { - return getPreferences().getBoolean("manually_change_presence", true); - } - - protected void unregisterNdefPushMessageCallback() { - NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this); - if (nfcAdapter != null && nfcAdapter.isEnabled()) { - nfcAdapter.setNdefPushMessageCallback(null,this); - } - } - - protected String getShareableUri() { - return null; - } - - private void inviteUser() { - Account mAccount = xmppConnectionService.getAccounts().get(0); - String user = mAccount.getJid().getLocalpart().toString(); - String domain = mAccount.getJid().getDomainpart().toString(); - String inviteURL = Config.inviteUserURL + user + "/" + domain; - String inviteText = getString(R.string.InviteText, user); - Intent intent = new Intent(android.content.Intent.ACTION_SEND); - intent.setType("text/plain"); - intent.putExtra(Intent.EXTRA_SUBJECT, user + " " + getString(R.string.inviteUser_Subject) + " " + getString(R.string.app_name)); - intent.putExtra(Intent.EXTRA_TEXT, inviteText + "\n\n" + inviteURL); - startActivity(Intent.createChooser(intent, getString(R.string.invite_contact))); - } - - private void createIssue() { - String IssueURL = Config.ISSUE_URL; - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(IssueURL)); - startActivity(intent); - } - - protected void shareUri() { - String uri = getShareableUri(); - if (uri == null || uri.isEmpty()) { - return; - } - 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 void onResume() { - super.onResume(); - if (this.getShareableUri()!=null) { - this.registerNdefPushMessageCallback(); - } - } - - protected int findTheme() { - if (getPreferences().getBoolean("use_larger_font", false)) { - return R.style.ConversationsTheme_LargerText; - } else { - return R.style.ConversationsTheme; - } - } - - @Override - public void onPause() { - super.onPause(); - this.unregisterNdefPushMessageCallback(); - } - - protected void showQrCode() { - String uri = getShareableUri(); - if (uri!=null) { - Point size = new Point(); - getWindowManager().getDefaultDisplay().getSize(size); - final int width = (size.x < size.y ? size.x : size.y); - Bitmap bitmap = createQrCodeBitmap(uri, width); - ImageView view = new ImageView(this); - view.setImageBitmap(bitmap); - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setView(view); - builder.create().show(); - } - } - - protected Bitmap createQrCodeBitmap(String input, int size) { - Log.d(Config.LOGTAG,"qr code requested size: "+size); - try { - final QRCodeWriter QR_CODE_WRITER = new QRCodeWriter(); - final Hashtable hints = new Hashtable<>(); - hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M); - final BitMatrix result = QR_CODE_WRITER.encode(input, BarcodeFormat.QR_CODE, size, size, hints); - final int width = result.getWidth(); - final int height = result.getHeight(); - final int[] pixels = new int[width * height]; - for (int y = 0; y < height; y++) { - final int offset = y * width; - for (int x = 0; x < width; x++) { - pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.TRANSPARENT; - } - } - final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - Log.d(Config.LOGTAG,"output size: "+width+"x"+height); - bitmap.setPixels(pixels, 0, width, 0, 0, width, height); - return bitmap; - } catch (final WriterException e) { - return null; - } - } - - protected Account extractAccount(Intent intent) { - String jid = intent != null ? intent.getStringExtra(EXTRA_ACCOUNT) : null; - try { - return jid != null ? xmppConnectionService.findAccountByJid(Jid.fromString(jid)) : null; - } catch (InvalidJidException e) { - return null; - } - } - - public static class ConferenceInvite { - private String uuid; - private List jids = new ArrayList<>(); - - public static ConferenceInvite parse(Intent data) { - ConferenceInvite invite = new ConferenceInvite(); - invite.uuid = data.getStringExtra("conversation"); - if (invite.uuid == null) { - return null; - } - try { - if (data.getBooleanExtra("multiple", false)) { - String[] toAdd = data.getStringArrayExtra("contacts"); - for (String item : toAdd) { - invite.jids.add(Jid.fromString(item)); - } - } else { - invite.jids.add(Jid.fromString(data.getStringExtra("contact"))); - } - } catch (final InvalidJidException ignored) { - return null; - } - return invite; - } - - public boolean execute(XmppActivity activity) { - XmppConnectionService service = activity.xmppConnectionService; - Conversation conversation = service.findConversationByUuid(this.uuid); - if (conversation == null) { - return false; - } - if (conversation.getMode() == Conversation.MODE_MULTI) { - for (Jid jid : jids) { - service.invite(conversation, jid); - } - return false; - } else { - jids.add(conversation.getJid().toBareJid()); - service.createAdhocConference(conversation.getAccount(), null, jids, activity.adhocCallback); - return true; - } - } - } - - public AvatarService avatarService() { - return xmppConnectionService.getAvatarService(); - } - - class BitmapWorkerTask extends AsyncTask { - private final WeakReference imageViewReference; - private Message message = null; - - public BitmapWorkerTask(ImageView imageView) { - imageViewReference = new WeakReference<>(imageView); - } - - @Override - protected Bitmap doInBackground(Message... params) { - if (isCancelled()) { - return null; - } - message = params[0]; - try { - return xmppConnectionService.getFileBackend().getThumbnail( - message, (int) (metrics.density * 288), false); - } catch (FileNotFoundException e) { - return null; - } - } - - @Override - protected void onPostExecute(Bitmap bitmap) { - if (bitmap != null && !isCancelled()) { - final ImageView imageView = imageViewReference.get(); - if (imageView != null) { - imageView.setImageBitmap(bitmap); - imageView.setBackgroundColor(0x00000000); - } - } - } - } - - public void loadBitmap(Message message, ImageView imageView) { - Bitmap bm; - try { - bm = xmppConnectionService.getFileBackend().getThumbnail(message, - (int) (metrics.density * 288), true); - } catch (FileNotFoundException e) { - bm = null; - } - if (bm != null) { - cancelPotentialWork(message, imageView); - imageView.setImageBitmap(bm); - imageView.setBackgroundColor(0x00000000); - } else { - if (cancelPotentialWork(message, imageView)) { - imageView.setBackgroundColor(0xff333333); - imageView.setImageDrawable(null); - final BitmapWorkerTask task = new BitmapWorkerTask(imageView); - final AsyncDrawable asyncDrawable = new AsyncDrawable( - getResources(), null, task); - imageView.setImageDrawable(asyncDrawable); - try { - task.execute(message); - } catch (final RejectedExecutionException ignored) { - ignored.printStackTrace(); - } - } - } - } - - public static boolean cancelPotentialWork(Message message, ImageView imageView) { - final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); - - if (bitmapWorkerTask != null) { - final Message oldMessage = bitmapWorkerTask.message; - if (oldMessage == null || message != oldMessage) { - bitmapWorkerTask.cancel(true); - } else { - return false; - } - } - return true; - } - - private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { - if (imageView != null) { - final Drawable drawable = imageView.getDrawable(); - if (drawable instanceof AsyncDrawable) { - final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; - return asyncDrawable.getBitmapWorkerTask(); - } - } - return null; - } - - static class AsyncDrawable extends BitmapDrawable { - private final WeakReference bitmapWorkerTaskReference; - - public AsyncDrawable(Resources res, Bitmap bitmap, - BitmapWorkerTask bitmapWorkerTask) { - super(res, bitmap); - bitmapWorkerTaskReference = new WeakReference<>( - bitmapWorkerTask); - } - - public BitmapWorkerTask getBitmapWorkerTask() { - return bitmapWorkerTaskReference.get(); - } - } + protected static final int REQUEST_ANNOUNCE_PGP = 0x0101; + protected static final int REQUEST_INVITE_TO_CONVERSATION = 0x0102; + protected static final int REQUEST_CHOOSE_PGP_ID = 0x0103; + protected static final int REQUEST_BATTERY_OP = 0x13849ff; + + public static final String EXTRA_ACCOUNT = "account"; + + public XmppConnectionService xmppConnectionService; + public boolean xmppConnectionServiceBound = false; + protected boolean registeredListeners = false; + + protected int mPrimaryTextColor; + protected int mSecondaryTextColor; + protected int mTertiaryTextColor; + protected int mPrimaryBackgroundColor; + protected int mSecondaryBackgroundColor; + protected int mColorRed; + protected int mColorWhite; + protected int mColorOrange; + protected int mColorGreen; + protected int mPrimaryColor; + + protected boolean mUseSubject = true; + + private DisplayMetrics metrics; + protected int mTheme; + protected boolean mUsingEnterKey = false; + + protected Toast mToast; + + protected void hideToast() { + if (mToast != null) { + mToast.cancel(); + } + } + + protected void replaceToast(String msg) { + replaceToast(msg, true); + } + + protected void replaceToast(String msg, boolean showlong) { + hideToast(); + mToast = Toast.makeText(this, msg, showlong ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT); + mToast.show(); + } + + public void showProgress() { + + } + + public void closeProgress() { + + } + + protected Runnable onOpenPGPKeyPublished = new Runnable() { + @Override + public void run() { + Toast.makeText(XmppActivity.this, R.string.openpgp_has_been_published, Toast.LENGTH_SHORT).show(); + } + }; + + private long mLastUiRefresh = 0; + private Handler mRefreshUiHandler = new Handler(); + private Runnable mRefreshUiRunnable = new Runnable() { + @Override + public void run() { + mLastUiRefresh = SystemClock.elapsedRealtime(); + refreshUiReal(); + } + }; + + protected ConferenceInvite mPendingConferenceInvite = null; + + + protected final void refreshUi() { + final long diff = SystemClock.elapsedRealtime() - mLastUiRefresh; + if (diff > Config.REFRESH_UI_INTERVAL) { + mRefreshUiHandler.removeCallbacks(mRefreshUiRunnable); + runOnUiThread(mRefreshUiRunnable); + } else { + final long next = Config.REFRESH_UI_INTERVAL - diff; + mRefreshUiHandler.removeCallbacks(mRefreshUiRunnable); + mRefreshUiHandler.postDelayed(mRefreshUiRunnable, next); + } + } + + abstract protected void refreshUiReal(); + + protected interface OnValueEdited { + public void onValueEdited(String value); + } + + public interface OnPresenceSelected { + public void onPresenceSelected(); + } + + protected ServiceConnection mConnection = new ServiceConnection() { + + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + XmppConnectionBinder binder = (XmppConnectionBinder) service; + xmppConnectionService = binder.getService(); + xmppConnectionServiceBound = true; + if (!registeredListeners && shouldRegisterListeners()) { + registerListeners(); + registeredListeners = true; + } + onBackendConnected(); + } + + @Override + public void onServiceDisconnected(ComponentName arg0) { + xmppConnectionServiceBound = false; + } + }; + + @Override + protected void onStart() { + super.onStart(); + if (!xmppConnectionServiceBound) { + connectToBackend(); + } else { + if (!registeredListeners) { + this.registerListeners(); + this.registeredListeners = true; + } + this.onBackendConnected(); + } + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) + protected boolean shouldRegisterListeners() { + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + return !isDestroyed() && !isFinishing(); + } else { + return !isFinishing(); + } + } + + public void connectToBackend() { + Intent intent = new Intent(this, XmppConnectionService.class); + intent.setAction("ui"); + startService(intent); + bindService(intent, mConnection, Context.BIND_AUTO_CREATE); + } + + @Override + protected void onStop() { + super.onStop(); + if (xmppConnectionServiceBound) { + if (registeredListeners) { + this.unregisterListeners(); + this.registeredListeners = false; + } + unbindService(mConnection); + xmppConnectionServiceBound = false; + } + } + + protected void hideKeyboard() { + InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + + View focus = getCurrentFocus(); + + if (focus != null) { + + inputManager.hideSoftInputFromWindow(focus.getWindowToken(), + InputMethodManager.HIDE_NOT_ALWAYS); + } + } + + public boolean hasPgp() { + return xmppConnectionService.getPgpEngine() != null; + } + + public void showInstallPgpDialog() { + Builder builder = new AlertDialog.Builder(this); + builder.setTitle(getString(R.string.openkeychain_required)); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + builder.setMessage(getText(R.string.openkeychain_required_long)); + builder.setNegativeButton(getString(R.string.cancel), null); + builder.setNeutralButton(getString(R.string.restart), + new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + if (xmppConnectionServiceBound) { + unbindService(mConnection); + xmppConnectionServiceBound = false; + } + stopService(new Intent(XmppActivity.this, + XmppConnectionService.class)); + finish(); + } + }); + builder.setPositiveButton(getString(R.string.install), + new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + Uri uri = Uri + .parse("market://details?id=org.sufficientlysecure.keychain"); + Intent marketIntent = new Intent(Intent.ACTION_VIEW, + uri); + PackageManager manager = getApplicationContext() + .getPackageManager(); + List infos = manager + .queryIntentActivities(marketIntent, 0); + if (infos.size() > 0) { + startActivity(marketIntent); + } else { + uri = Uri.parse("http://www.openkeychain.org/"); + Intent browserIntent = new Intent( + Intent.ACTION_VIEW, uri); + startActivity(browserIntent); + } + finish(); + } + }); + builder.create().show(); + } + + abstract void onBackendConnected(); + + protected void registerListeners() { + if (this instanceof XmppConnectionService.OnConversationUpdate) { + this.xmppConnectionService.setOnConversationListChangedListener((XmppConnectionService.OnConversationUpdate) this); + } + if (this instanceof XmppConnectionService.OnAccountUpdate) { + this.xmppConnectionService.setOnAccountListChangedListener((XmppConnectionService.OnAccountUpdate) this); + } + if (this instanceof XmppConnectionService.OnCaptchaRequested) { + this.xmppConnectionService.setOnCaptchaRequestedListener((XmppConnectionService.OnCaptchaRequested) this); + } + if (this instanceof XmppConnectionService.OnRosterUpdate) { + this.xmppConnectionService.setOnRosterUpdateListener((XmppConnectionService.OnRosterUpdate) this); + } + if (this instanceof XmppConnectionService.OnMucRosterUpdate) { + this.xmppConnectionService.setOnMucRosterUpdateListener((XmppConnectionService.OnMucRosterUpdate) this); + } + if (this instanceof OnUpdateBlocklist) { + this.xmppConnectionService.setOnUpdateBlocklistListener((OnUpdateBlocklist) this); + } + if (this instanceof XmppConnectionService.OnShowErrorToast) { + this.xmppConnectionService.setOnShowErrorToastListener((XmppConnectionService.OnShowErrorToast) this); + } + if (this instanceof OnKeyStatusUpdated) { + this.xmppConnectionService.setOnKeyStatusUpdatedListener((OnKeyStatusUpdated) this); + } + } + + protected void unregisterListeners() { + if (this instanceof XmppConnectionService.OnConversationUpdate) { + this.xmppConnectionService.removeOnConversationListChangedListener(); + } + if (this instanceof XmppConnectionService.OnAccountUpdate) { + this.xmppConnectionService.removeOnAccountListChangedListener(); + } + if (this instanceof XmppConnectionService.OnCaptchaRequested) { + this.xmppConnectionService.removeOnCaptchaRequestedListener(); + } + if (this instanceof XmppConnectionService.OnRosterUpdate) { + this.xmppConnectionService.removeOnRosterUpdateListener(); + } + if (this instanceof XmppConnectionService.OnMucRosterUpdate) { + this.xmppConnectionService.removeOnMucRosterUpdateListener(); + } + if (this instanceof OnUpdateBlocklist) { + this.xmppConnectionService.removeOnUpdateBlocklistListener(); + } + if (this instanceof XmppConnectionService.OnShowErrorToast) { + this.xmppConnectionService.removeOnShowErrorToastListener(); + } + if (this instanceof OnKeyStatusUpdated) { + this.xmppConnectionService.removeOnNewKeysAvailableListener(); + } + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + switch (item.getItemId()) { + case R.id.action_invite_user: + inviteUser(); + break; + case R.id.action_create_issue: + createIssue(); + break; + case R.id.action_settings: + startActivity(new Intent(this, SettingsActivity.class)); + break; + case R.id.action_check_updates: + if (xmppConnectionService.hasInternetConnection()) { + startActivity(new Intent(this, UpdaterActivity.class)); + } else { + Toast.makeText(this, R.string.account_status_no_internet, Toast.LENGTH_LONG).show(); + } + break; + case R.id.action_accounts: + final Intent intent = new Intent(getApplicationContext(), EditAccountActivity.class); + Account mAccount = xmppConnectionService.getAccounts().get(0); + intent.putExtra("jid", mAccount.getJid().toBareJid().toString()); + intent.putExtra("init", false); + startActivity(intent); + break; + case android.R.id.home: + finish(); + break; + case R.id.action_show_qr_code: + showQrCode(); + break; + } + return super.onOptionsItemSelected(item); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + metrics = getResources().getDisplayMetrics(); + ExceptionHelper.init(getApplicationContext()); + mPrimaryTextColor = getResources().getColor(R.color.black87); + mSecondaryTextColor = getResources().getColor(R.color.black54); + mTertiaryTextColor = getResources().getColor(R.color.black12); + mColorRed = getResources().getColor(R.color.red800); + mColorWhite = getResources().getColor(R.color.white70); + mColorOrange = getResources().getColor(R.color.orange500); + mColorGreen = getResources().getColor(R.color.realgreen); + mPrimaryColor = getResources().getColor(R.color.primary); + mPrimaryBackgroundColor = getResources().getColor(R.color.grey50); + mSecondaryBackgroundColor = getResources().getColor(R.color.grey200); + this.mTheme = findTheme(); + setTheme(this.mTheme); + this.mUsingEnterKey = usingEnterKey(); + mUseSubject = getPreferences().getBoolean("use_subject", true); + final ActionBar ab = getActionBar(); + if (ab != null) { + ab.setDisplayHomeAsUpEnabled(true); + } + } + + protected boolean isOptimizingBattery() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); + return !pm.isIgnoringBatteryOptimizations(getPackageName()); + } else { + return false; + } + } + + protected boolean isAffectedByDataSaver() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); + return cm.isActiveNetworkMetered() + && cm.getRestrictBackgroundStatus() == ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED; + } else { + return false; + } + } + + 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); + } + + public void switchToConversation(Conversation conversation, String text, + boolean newTask) { + switchToConversation(conversation, text, null, false, newTask); + } + + public void highlightInMuc(Conversation conversation, String nick) { + switchToConversation(conversation, null, nick, false, false); + } + + public void privateMsgInMuc(Conversation conversation, String nick) { + switchToConversation(conversation, null, nick, true, false); + } + + private void switchToConversation(Conversation conversation, String text, String nick, boolean pm, boolean newTask) { + Intent viewConversationIntent = new Intent(this, + ConversationActivity.class); + viewConversationIntent.setAction(ConversationActivity.ACTION_VIEW_CONVERSATION); + viewConversationIntent.putExtra(ConversationActivity.CONVERSATION, + conversation.getUuid()); + if (text != null) { + viewConversationIntent.putExtra(ConversationActivity.TEXT, text); + } + if (nick != null) { + viewConversationIntent.putExtra(ConversationActivity.NICK, nick); + viewConversationIntent.putExtra(ConversationActivity.PRIVATE_MESSAGE, pm); + } + if (newTask) { + viewConversationIntent.setFlags(viewConversationIntent.getFlags() + | Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_SINGLE_TOP); + } else { + viewConversationIntent.setFlags(viewConversationIntent.getFlags() + | Intent.FLAG_ACTIVITY_CLEAR_TOP); + } + startActivity(viewConversationIntent); + finish(); + } + + public void switchToContactDetails(Contact contact) { + switchToContactDetails(contact, null); + } + + public void switchToContactDetails(Contact contact, String messageFingerprint) { + Intent intent = new Intent(this, ContactDetailsActivity.class); + intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT); + intent.putExtra(EXTRA_ACCOUNT, contact.getAccount().getJid().toBareJid().toString()); + intent.putExtra("contact", contact.getJid().toString()); + intent.putExtra("fingerprint", messageFingerprint); + startActivity(intent); + } + + public void switchToAccount(Account account) { + switchToAccount(account, false); + } + + public void switchToAccount(Account account, boolean init) { + Intent intent = new Intent(this, EditAccountActivity.class); + intent.putExtra("jid", account.getJid().toBareJid().toString()); + intent.putExtra("init", init); + startActivity(intent); + } + + protected void inviteToConversation(Conversation conversation) { + Intent intent = new Intent(getApplicationContext(), + ChooseContactActivity.class); + List contacts = new ArrayList<>(); + if (conversation.getMode() == Conversation.MODE_MULTI) { + for (MucOptions.User user : conversation.getMucOptions().getUsers(false)) { + Jid jid = user.getRealJid(); + if (jid != null) { + contacts.add(jid.toBareJid().toString()); + } + } + } else { + contacts.add(conversation.getJid().toBareJid().toString()); + } + intent.putExtra("filter_contacts", contacts.toArray(new String[contacts.size()])); + intent.putExtra("conversation", conversation.getUuid()); + intent.putExtra("multiple", true); + intent.putExtra("show_enter_jid", true); + intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().toBareJid().toString()); + startActivityForResult(intent, REQUEST_INVITE_TO_CONVERSATION); + } + + protected void announcePgp(Account account, final Conversation conversation, final Runnable onSuccess) { + if (account.getPgpId() == 0) { + choosePgpSignId(account); + } else { + String status = null; + if (manuallyChangePresence()) { + status = account.getPresenceStatusMessage(); + } + if (status == null) { + status = ""; + } + xmppConnectionService.getPgpEngine().generateSignature(account, status, new UiCallback() { + + @Override + public void userInputRequried(PendingIntent pi, Account account) { + try { + startIntentSenderForResult(pi.getIntentSender(), REQUEST_ANNOUNCE_PGP, null, 0, 0, 0); + } catch (final SendIntentException ignored) { + } + } + + @Override + public void success(Account account) { + xmppConnectionService.databaseBackend.updateAccount(account); + xmppConnectionService.sendPresence(account); + if (conversation != null) { + conversation.setNextEncryption(Message.ENCRYPTION_PGP); + xmppConnectionService.updateConversation(conversation); + refreshUi(); + } + if (onSuccess != null) { + runOnUiThread(onSuccess); + } + } + + @Override + public void error(int error, Account account) { + if (error == 0 && account != null) { + account.setPgpSignId(0); + account.unsetPgpSignature(); + xmppConnectionService.databaseBackend.updateAccount(account); + choosePgpSignId(account); + } else { + displayErrorDialog(error); + } + } + }); + } + } + + protected boolean noAccountUsesPgp() { + if (!hasPgp()) { + return true; + } + for (Account account : xmppConnectionService.getAccounts()) { + if (account.getPgpId() != 0) { + return false; + } + } + return true; + } + + @SuppressWarnings("deprecation") + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + protected void setListItemBackgroundOnView(View view) { + int sdk = android.os.Build.VERSION.SDK_INT; + if (sdk < android.os.Build.VERSION_CODES.JELLY_BEAN) { + view.setBackgroundDrawable(getResources().getDrawable(R.drawable.greybackground)); + } else { + view.setBackground(getResources().getDrawable(R.drawable.greybackground)); + } + } + + protected void choosePgpSignId(Account account) { + xmppConnectionService.getPgpEngine().chooseKey(account, new UiCallback() { + @Override + public void success(Account account1) { + } + + @Override + public void error(int errorCode, Account object) { + + } + + @Override + public void userInputRequried(PendingIntent pi, Account object) { + try { + startIntentSenderForResult(pi.getIntentSender(), + REQUEST_CHOOSE_PGP_ID, null, 0, 0, 0); + } catch (final SendIntentException ignored) { + } + } + }); + } + + protected void displayErrorDialog(final int errorCode) { + runOnUiThread(new Runnable() { + + @Override + public void run() { + AlertDialog.Builder builder = new AlertDialog.Builder( + XmppActivity.this); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + builder.setTitle(getString(R.string.error)); + builder.setMessage(errorCode); + builder.setNeutralButton(R.string.accept, null); + builder.create().show(); + } + }); + + } + + protected void showAddToRosterDialog(final Conversation conversation) { + showAddToRosterDialog(conversation.getContact()); + } + + protected void showAddToRosterDialog(final Contact contact) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(contact.getJid().toString()); + builder.setMessage(getString(R.string.not_in_roster)); + builder.setNegativeButton(getString(R.string.cancel), null); + builder.setPositiveButton(getString(R.string.add_contact), + new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + final Jid jid = contact.getJid(); + Account account = contact.getAccount(); + Contact contact = account.getRoster().getContact(jid); + xmppConnectionService.createContact(contact); + } + }); + builder.create().show(); + } + + private void showAskForPresenceDialog(final Contact contact) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(contact.getJid().toString()); + builder.setMessage(R.string.request_presence_updates); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(R.string.request_now, + new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + if (xmppConnectionServiceBound) { + xmppConnectionService.sendPresencePacket(contact + .getAccount(), xmppConnectionService + .getPresenceGenerator() + .requestPresenceUpdatesFrom(contact)); + } + } + }); + builder.create().show(); + } + + private void warnMutalPresenceSubscription(final Conversation conversation, + final OnPresenceSelected listener) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(conversation.getContact().getJid().toString()); + builder.setMessage(R.string.without_mutual_presence_updates); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(R.string.ignore, new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + conversation.setNextCounterpart(null); + if (listener != null) { + listener.onPresenceSelected(); + } + } + }); + builder.create().show(); + } + + protected void quickEdit(String previousValue, int hint, OnValueEdited callback) { + quickEdit(previousValue, callback, hint, false); + } + + protected void quickPasswordEdit(String previousValue, OnValueEdited callback) { + quickEdit(previousValue, callback, R.string.password, true); + } + + @SuppressLint("InflateParams") + private void quickEdit(final String previousValue, + final OnValueEdited callback, + final int hint, + boolean password) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + View view = getLayoutInflater().inflate(R.layout.quickedit, null); + final EditText editor = (EditText) view.findViewById(R.id.editor); + OnClickListener mClickListener = new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + String value = editor.getText().toString(); + if (!value.equals(previousValue) && value.trim().length() > 0) { + callback.onValueEdited(value); + } + } + }; + if (password) { + editor.setInputType(InputType.TYPE_CLASS_TEXT + | InputType.TYPE_TEXT_VARIATION_PASSWORD); + builder.setPositiveButton(R.string.accept, mClickListener); + } else { + builder.setPositiveButton(R.string.edit, mClickListener); + } + if (hint != 0) { + editor.setHint(hint); + } + editor.requestFocus(); + editor.setText(""); + if (previousValue != null) { + editor.getText().append(previousValue); + } + builder.setView(view); + builder.setNegativeButton(R.string.cancel, null); + builder.create().show(); + } + + public boolean hasStoragePermission(int requestCode) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestCode); + return false; + } else { + return true; + } + } else { + return true; + } + } + + public boolean hasMicPermission(int requestCode) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { + requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, requestCode); + return false; + } else { + return true; + } + } else { + return true; + } + } + + public boolean hasLocationPermission(int requestCode) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED || checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, requestCode); + requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, requestCode); + return false; + } else { + return true; + } + } else { + return true; + } + } + + public void selectPresence(final Conversation conversation, + final OnPresenceSelected listener) { + final Contact contact = conversation.getContact(); + if (conversation.hasValidOtrSession()) { + SessionID id = conversation.getOtrSession().getSessionID(); + Jid jid; + try { + jid = Jid.fromString(id.getAccountID() + "/" + id.getUserID()); + } catch (InvalidJidException e) { + jid = null; + } + conversation.setNextCounterpart(jid); + listener.onPresenceSelected(); + } else if (!contact.showInRoster()) { + showAddToRosterDialog(conversation); + } else { + final Presences presences = contact.getPresences(); + if (presences.size() == 0) { + if (!contact.getOption(Contact.Options.TO) + && !contact.getOption(Contact.Options.ASKING) + && contact.getAccount().getStatus() == Account.State.ONLINE) { + showAskForPresenceDialog(contact); + } else if (!contact.getOption(Contact.Options.TO) + || !contact.getOption(Contact.Options.FROM)) { + warnMutalPresenceSubscription(conversation, listener); + } else { + conversation.setNextCounterpart(null); + listener.onPresenceSelected(); + } + } else if (presences.size() == 1) { + String presence = presences.toResourceArray()[0]; + try { + conversation.setNextCounterpart(Jid.fromParts(contact.getJid().getLocalpart(), contact.getJid().getDomainpart(), presence)); + } catch (InvalidJidException e) { + conversation.setNextCounterpart(null); + } + listener.onPresenceSelected(); + } else { + showPresenceSelectionDialog(presences, conversation, listener); + } + } + } + + private void showPresenceSelectionDialog(Presences presences, final Conversation conversation, final OnPresenceSelected listener) { + final Contact contact = conversation.getContact(); + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(getString(R.string.choose_presence)); + final String[] resourceArray = presences.toResourceArray(); + Pair, Map> typeAndName = presences.toTypeAndNameMap(); + final Map resourceTypeMap = typeAndName.first; + final Map resourceNameMap = typeAndName.second; + final String[] readableIdentities = new String[resourceArray.length]; + final AtomicInteger selectedResource = new AtomicInteger(0); + for (int i = 0; i < resourceArray.length; ++i) { + String resource = resourceArray[i]; + if (resource.equals(contact.getLastResource())) { + selectedResource.set(i); + } + String type = resourceTypeMap.get(resource); + String name = resourceNameMap.get(resource); + if (type != null) { + if (Collections.frequency(resourceTypeMap.values(), type) == 1) { + readableIdentities[i] = UIHelper.tranlasteType(this, type); + } else if (name != null) { + if (Collections.frequency(resourceNameMap.values(), name) == 1 + || CryptoHelper.UUID_PATTERN.matcher(resource).matches()) { + readableIdentities[i] = UIHelper.tranlasteType(this, type) + " (" + name + ")"; + } else { + readableIdentities[i] = UIHelper.tranlasteType(this, type) + " (" + name + " / " + resource + ")"; + } + } else { + readableIdentities[i] = UIHelper.tranlasteType(this, type) + " (" + resource + ")"; + } + } else { + readableIdentities[i] = resource; + } + } + builder.setSingleChoiceItems(readableIdentities, + selectedResource.get(), + new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + selectedResource.set(which); + } + }); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(R.string.ok, new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + try { + Jid next = Jid.fromParts(contact.getJid().getLocalpart(), contact.getJid().getDomainpart(), resourceArray[selectedResource.get()]); + conversation.setNextCounterpart(next); + } catch (InvalidJidException e) { + conversation.setNextCounterpart(null); + } + listener.onPresenceSelected(); + } + }); + builder.create().show(); + } + + protected void onActivityResult(int requestCode, int resultCode, final Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_INVITE_TO_CONVERSATION && resultCode == RESULT_OK) { + mPendingConferenceInvite = ConferenceInvite.parse(data); + if (xmppConnectionServiceBound && mPendingConferenceInvite != null) { + if (mPendingConferenceInvite.execute(this)) { + mToast = Toast.makeText(this, R.string.creating_conference, Toast.LENGTH_LONG); + mToast.show(); + } + mPendingConferenceInvite = null; + } + } + } + + + private UiCallback adhocCallback = new UiCallback() { + @Override + public void success(final Conversation conversation) { + runOnUiThread(new Runnable() { + @Override + public void run() { + switchToConversation(conversation); + hideToast(); + } + }); + } + + @Override + public void error(final int errorCode, Conversation object) { + runOnUiThread(new Runnable() { + @Override + public void run() { + replaceToast(getString(errorCode)); + } + }); + } + + @Override + public void userInputRequried(PendingIntent pi, Conversation object) { + + } + }; + + public int getTertiaryTextColor() { + return this.mTertiaryTextColor; + } + + public int getSecondaryTextColor() { + return this.mSecondaryTextColor; + } + + public int getPrimaryTextColor() { + return this.mPrimaryTextColor; + } + + public int getWarningTextColor() { + return this.mColorRed; + } + + public int getUnencryptedTextColor() { + return this.mColorWhite; + } + + public int getOnlineColor() { + return this.mColorGreen; + } + + public int getPrimaryBackgroundColor() { + return this.mPrimaryBackgroundColor; + } + + public int getSecondaryBackgroundColor() { + return this.mSecondaryBackgroundColor; + } + + public int getPixel(int dp) { + DisplayMetrics metrics = getResources().getDisplayMetrics(); + return ((int) (dp * metrics.density)); + } + + public boolean copyTextToClipboard(String text, int labelResId) { + ClipboardManager mClipBoardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); + String label = getResources().getString(labelResId); + if (mClipBoardManager != null) { + ClipData mClipData = ClipData.newPlainText(label, text); + mClipBoardManager.setPrimaryClip(mClipData); + return true; + } + return false; + } + + protected void registerNdefPushMessageCallback() { + NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this); + if (nfcAdapter != null && nfcAdapter.isEnabled()) { + nfcAdapter.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() { + @Override + public NdefMessage createNdefMessage(NfcEvent nfcEvent) { + return new NdefMessage(new NdefRecord[]{ + NdefRecord.createUri(getShareableUri()), + NdefRecord.createApplicationRecord("de.pixart.messenger") + }); + } + }, this); + } + } + + protected boolean neverCompressPictures() { + return getPreferences().getString("picture_compression", "auto").equals("never"); + } + + protected boolean manuallyChangePresence() { + return getPreferences().getBoolean("manually_change_presence", true); + } + + protected void unregisterNdefPushMessageCallback() { + NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this); + if (nfcAdapter != null && nfcAdapter.isEnabled()) { + nfcAdapter.setNdefPushMessageCallback(null, this); + } + } + + protected String getShareableUri() { + return null; + } + + private void inviteUser() { + Account mAccount = xmppConnectionService.getAccounts().get(0); + String user = mAccount.getJid().getLocalpart().toString(); + String domain = mAccount.getJid().getDomainpart().toString(); + String inviteURL = Config.inviteUserURL + user + "/" + domain; + String inviteText = getString(R.string.InviteText, user); + Intent intent = new Intent(android.content.Intent.ACTION_SEND); + intent.setType("text/plain"); + intent.putExtra(Intent.EXTRA_SUBJECT, user + " " + getString(R.string.inviteUser_Subject) + " " + getString(R.string.app_name)); + intent.putExtra(Intent.EXTRA_TEXT, inviteText + "\n\n" + inviteURL); + startActivity(Intent.createChooser(intent, getString(R.string.invite_contact))); + } + + private void createIssue() { + String IssueURL = Config.ISSUE_URL; + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(IssueURL)); + startActivity(intent); + } + + protected void shareUri() { + String uri = getShareableUri(); + if (uri == null || uri.isEmpty()) { + return; + } + 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 void onResume() { + super.onResume(); + if (this.getShareableUri() != null) { + this.registerNdefPushMessageCallback(); + } + } + + protected int findTheme() { + if (getPreferences().getBoolean("use_larger_font", false)) { + return R.style.ConversationsTheme_LargerText; + } else { + return R.style.ConversationsTheme; + } + } + + @Override + public void onPause() { + super.onPause(); + this.unregisterNdefPushMessageCallback(); + } + + protected void showQrCode() { + String uri = getShareableUri(); + if (uri != null) { + Point size = new Point(); + getWindowManager().getDefaultDisplay().getSize(size); + final int width = (size.x < size.y ? size.x : size.y); + Bitmap bitmap = createQrCodeBitmap(uri, width); + ImageView view = new ImageView(this); + view.setImageBitmap(bitmap); + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setView(view); + builder.create().show(); + } + } + + protected Bitmap createQrCodeBitmap(String input, int size) { + Log.d(Config.LOGTAG, "qr code requested size: " + size); + try { + final QRCodeWriter QR_CODE_WRITER = new QRCodeWriter(); + final Hashtable hints = new Hashtable<>(); + hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M); + final BitMatrix result = QR_CODE_WRITER.encode(input, BarcodeFormat.QR_CODE, size, size, hints); + final int width = result.getWidth(); + final int height = result.getHeight(); + final int[] pixels = new int[width * height]; + for (int y = 0; y < height; y++) { + final int offset = y * width; + for (int x = 0; x < width; x++) { + pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.TRANSPARENT; + } + } + final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Log.d(Config.LOGTAG, "output size: " + width + "x" + height); + bitmap.setPixels(pixels, 0, width, 0, 0, width, height); + return bitmap; + } catch (final WriterException e) { + return null; + } + } + + protected Account extractAccount(Intent intent) { + String jid = intent != null ? intent.getStringExtra(EXTRA_ACCOUNT) : null; + try { + return jid != null ? xmppConnectionService.findAccountByJid(Jid.fromString(jid)) : null; + } catch (InvalidJidException e) { + return null; + } + } + + public static class ConferenceInvite { + private String uuid; + private List jids = new ArrayList<>(); + + public static ConferenceInvite parse(Intent data) { + ConferenceInvite invite = new ConferenceInvite(); + invite.uuid = data.getStringExtra("conversation"); + if (invite.uuid == null) { + return null; + } + try { + if (data.getBooleanExtra("multiple", false)) { + String[] toAdd = data.getStringArrayExtra("contacts"); + for (String item : toAdd) { + invite.jids.add(Jid.fromString(item)); + } + } else { + invite.jids.add(Jid.fromString(data.getStringExtra("contact"))); + } + } catch (final InvalidJidException ignored) { + return null; + } + return invite; + } + + public boolean execute(XmppActivity activity) { + XmppConnectionService service = activity.xmppConnectionService; + Conversation conversation = service.findConversationByUuid(this.uuid); + if (conversation == null) { + return false; + } + if (conversation.getMode() == Conversation.MODE_MULTI) { + for (Jid jid : jids) { + service.invite(conversation, jid); + } + return false; + } else { + jids.add(conversation.getJid().toBareJid()); + service.createAdhocConference(conversation.getAccount(), null, jids, activity.adhocCallback); + return true; + } + } + } + + public AvatarService avatarService() { + return xmppConnectionService.getAvatarService(); + } + + class BitmapWorkerTask extends AsyncTask { + private final WeakReference imageViewReference; + private Message message = null; + + public BitmapWorkerTask(ImageView imageView) { + imageViewReference = new WeakReference<>(imageView); + } + + @Override + protected Bitmap doInBackground(Message... params) { + if (isCancelled()) { + return null; + } + message = params[0]; + try { + return xmppConnectionService.getFileBackend().getThumbnail( + message, (int) (metrics.density * 288), false); + } catch (FileNotFoundException e) { + return null; + } + } + + @Override + protected void onPostExecute(Bitmap bitmap) { + if (bitmap != null && !isCancelled()) { + final ImageView imageView = imageViewReference.get(); + if (imageView != null) { + imageView.setImageBitmap(bitmap); + imageView.setBackgroundColor(0x00000000); + } + } + } + } + + public void loadBitmap(Message message, ImageView imageView) { + Bitmap bm; + try { + bm = xmppConnectionService.getFileBackend().getThumbnail(message, + (int) (metrics.density * 288), true); + } catch (FileNotFoundException e) { + bm = null; + } + if (bm != null) { + cancelPotentialWork(message, imageView); + imageView.setImageBitmap(bm); + imageView.setBackgroundColor(0x00000000); + } else { + if (cancelPotentialWork(message, imageView)) { + imageView.setBackgroundColor(0xff333333); + imageView.setImageDrawable(null); + final BitmapWorkerTask task = new BitmapWorkerTask(imageView); + final AsyncDrawable asyncDrawable = new AsyncDrawable( + getResources(), null, task); + imageView.setImageDrawable(asyncDrawable); + try { + task.execute(message); + } catch (final RejectedExecutionException ignored) { + ignored.printStackTrace(); + } + } + } + } + + public static boolean cancelPotentialWork(Message message, ImageView imageView) { + final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); + + if (bitmapWorkerTask != null) { + final Message oldMessage = bitmapWorkerTask.message; + if (oldMessage == null || message != oldMessage) { + bitmapWorkerTask.cancel(true); + } else { + return false; + } + } + return true; + } + + private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { + if (imageView != null) { + final Drawable drawable = imageView.getDrawable(); + if (drawable instanceof AsyncDrawable) { + final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; + return asyncDrawable.getBitmapWorkerTask(); + } + } + return null; + } + + static class AsyncDrawable extends BitmapDrawable { + private final WeakReference bitmapWorkerTaskReference; + + public AsyncDrawable(Resources res, Bitmap bitmap, + BitmapWorkerTask bitmapWorkerTask) { + super(res, bitmap); + bitmapWorkerTaskReference = new WeakReference<>( + bitmapWorkerTask); + } + + public BitmapWorkerTask getBitmapWorkerTask() { + return bitmapWorkerTaskReference.get(); + } + } } \ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/adapter/AccountAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/AccountAdapter.java index 812c41ace..f8eb6f4f0 100644 --- a/src/main/java/de/pixart/messenger/ui/adapter/AccountAdapter.java +++ b/src/main/java/de/pixart/messenger/ui/adapter/AccountAdapter.java @@ -17,43 +17,43 @@ import de.pixart.messenger.ui.XmppActivity; public class AccountAdapter extends ArrayAdapter { - private XmppActivity activity; + private XmppActivity activity; - public AccountAdapter(XmppActivity activity, List objects) { - super(activity, 0, objects); - this.activity = activity; - } + public AccountAdapter(XmppActivity activity, List objects) { + super(activity, 0, objects); + this.activity = activity; + } - @Override - public View getView(int position, View view, ViewGroup parent) { - final Account account = getItem(position); - if (view == null) { - LayoutInflater inflater = (LayoutInflater) getContext() - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - view = inflater.inflate(R.layout.account_row, parent, false); - } - TextView jid = (TextView) view.findViewById(R.id.account_jid); - if (Config.DOMAIN_LOCK != null) { - jid.setText(account.getJid().getLocalpart()); - } else { - jid.setText(account.getJid().toBareJid().toString()); - } - TextView statusView = (TextView) view.findViewById(R.id.account_status); - ImageView imageView = (ImageView) view.findViewById(R.id.account_image); - imageView.setImageBitmap(activity.avatarService().get(account, activity.getPixel(56))); - statusView.setText(getContext().getString(account.getStatus().getReadableId())); - switch (account.getStatus()) { - case ONLINE: - statusView.setTextColor(activity.getOnlineColor()); - break; - case DISABLED: - case CONNECTING: - statusView.setTextColor(activity.getSecondaryTextColor()); - break; - default: - statusView.setTextColor(activity.getWarningTextColor()); - break; - } - return view; - } + @Override + public View getView(int position, View view, ViewGroup parent) { + final Account account = getItem(position); + if (view == null) { + LayoutInflater inflater = (LayoutInflater) getContext() + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + view = inflater.inflate(R.layout.account_row, parent, false); + } + TextView jid = (TextView) view.findViewById(R.id.account_jid); + if (Config.DOMAIN_LOCK != null) { + jid.setText(account.getJid().getLocalpart()); + } else { + jid.setText(account.getJid().toBareJid().toString()); + } + TextView statusView = (TextView) view.findViewById(R.id.account_status); + ImageView imageView = (ImageView) view.findViewById(R.id.account_image); + imageView.setImageBitmap(activity.avatarService().get(account, activity.getPixel(56))); + statusView.setText(getContext().getString(account.getStatus().getReadableId())); + switch (account.getStatus()) { + case ONLINE: + statusView.setTextColor(activity.getOnlineColor()); + break; + case DISABLED: + case CONNECTING: + statusView.setTextColor(activity.getSecondaryTextColor()); + break; + default: + statusView.setTextColor(activity.getWarningTextColor()); + break; + } + return view; + } } diff --git a/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java index f19dfa43b..d78c82cb2 100644 --- a/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java +++ b/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java @@ -29,13 +29,13 @@ import de.pixart.messenger.xmpp.chatstate.ChatState; public class ConversationAdapter extends ArrayAdapter { - private XmppActivity activity; + private XmppActivity activity; - public ConversationAdapter(XmppActivity activity, - List conversations) { - super(activity, 0, conversations); - this.activity = activity; - } + public ConversationAdapter(XmppActivity activity, + List conversations) { + super(activity, 0, conversations); + this.activity = activity; + } public static boolean cancelPotentialWork(Conversation conversation, ImageView imageView) { final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); @@ -62,8 +62,8 @@ public class ConversationAdapter extends ArrayAdapter { return null; } - @Override - public View getView(int position, View view, ViewGroup parent) { + @Override + public View getView(int position, View view, ViewGroup parent) { if (view == null) { LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); view = inflater.inflate(R.layout.conversation_list_row, parent, false); @@ -126,23 +126,23 @@ public class ConversationAdapter extends ArrayAdapter { } } } else {*/ - Pair preview = UIHelper.getMessagePreview(activity, message); - mLastMessage.setVisibility(View.VISIBLE); - imagePreview.setVisibility(View.GONE); - mLastMessage.setText(preview.first); - if (preview.second) { - if (conversation.isRead()) { - mLastMessage.setTypeface(null, Typeface.ITALIC); - } else { - mLastMessage.setTypeface(null, Typeface.BOLD_ITALIC); - } + Pair preview = UIHelper.getMessagePreview(activity, message); + mLastMessage.setVisibility(View.VISIBLE); + imagePreview.setVisibility(View.GONE); + mLastMessage.setText(preview.first); + if (preview.second) { + if (conversation.isRead()) { + mLastMessage.setTypeface(null, Typeface.ITALIC); } else { - if (conversation.isRead()) { - mLastMessage.setTypeface(null, Typeface.NORMAL); - } else { - mLastMessage.setTypeface(null, Typeface.BOLD); - } + mLastMessage.setTypeface(null, Typeface.BOLD_ITALIC); } + } else { + if (conversation.isRead()) { + mLastMessage.setTypeface(null, Typeface.NORMAL); + } else { + mLastMessage.setTypeface(null, Typeface.BOLD); + } + } //} long muted_till = conversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL, 0); @@ -168,41 +168,41 @@ public class ConversationAdapter extends ArrayAdapter { mLastMessage.setTypeface(null, Typeface.BOLD_ITALIC); } return view; - } - - public void loadAvatar(Conversation conversation, ImageView imageView) { - if (cancelPotentialWork(conversation, imageView)) { - final Bitmap bm = activity.avatarService().get(conversation, activity.getPixel(56), true); - if (bm != null) { - cancelPotentialWork(conversation, imageView); - imageView.setImageBitmap(bm); - imageView.setBackgroundColor(0x00000000); - } else { - imageView.setBackgroundColor(UIHelper.getColorForName(conversation.getName())); - imageView.setImageDrawable(null); - final BitmapWorkerTask task = new BitmapWorkerTask(imageView); - final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task); - imageView.setImageDrawable(asyncDrawable); - try { - task.execute(conversation); - } catch (final RejectedExecutionException ignored) { - } - } - } - } - - static class AsyncDrawable extends BitmapDrawable { - private final WeakReference bitmapWorkerTaskReference; - - public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) { - super(res, bitmap); - bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask); - } - - public BitmapWorkerTask getBitmapWorkerTask() { - return bitmapWorkerTaskReference.get(); - } - } + } + + public void loadAvatar(Conversation conversation, ImageView imageView) { + if (cancelPotentialWork(conversation, imageView)) { + final Bitmap bm = activity.avatarService().get(conversation, activity.getPixel(56), true); + if (bm != null) { + cancelPotentialWork(conversation, imageView); + imageView.setImageBitmap(bm); + imageView.setBackgroundColor(0x00000000); + } else { + imageView.setBackgroundColor(UIHelper.getColorForName(conversation.getName())); + imageView.setImageDrawable(null); + final BitmapWorkerTask task = new BitmapWorkerTask(imageView); + final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task); + imageView.setImageDrawable(asyncDrawable); + try { + task.execute(conversation); + } catch (final RejectedExecutionException ignored) { + } + } + } + } + + static class AsyncDrawable extends BitmapDrawable { + private final WeakReference bitmapWorkerTaskReference; + + public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) { + super(res, bitmap); + bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask); + } + + public BitmapWorkerTask getBitmapWorkerTask() { + return bitmapWorkerTaskReference.get(); + } + } class BitmapWorkerTask extends AsyncTask { private final WeakReference imageViewReference; diff --git a/src/main/java/de/pixart/messenger/ui/adapter/KnownHostsAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/KnownHostsAdapter.java index 2906f16ba..6096a7196 100644 --- a/src/main/java/de/pixart/messenger/ui/adapter/KnownHostsAdapter.java +++ b/src/main/java/de/pixart/messenger/ui/adapter/KnownHostsAdapter.java @@ -9,62 +9,62 @@ import java.util.List; import java.util.Locale; public class KnownHostsAdapter extends ArrayAdapter { - private ArrayList domains; - private Filter domainFilter = new Filter() { + private ArrayList domains; + private Filter domainFilter = new Filter() { - @Override - protected FilterResults performFiltering(CharSequence constraint) { - if (constraint != null) { - ArrayList suggestions = new ArrayList<>(); - final String[] split = constraint.toString().split("@"); - if (split.length == 1) { - for (String domain : domains) { - suggestions.add(split[0].toLowerCase(Locale - .getDefault()) + "@" + domain); - } - } else if (split.length == 2) { - for (String domain : domains) { - if (domain.contentEquals(split[1])) { - suggestions.clear(); - break; - } else if (domain.contains(split[1])) { - suggestions.add(split[0].toLowerCase(Locale - .getDefault()) + "@" + domain); - } - } - } else { - return new FilterResults(); - } - FilterResults filterResults = new FilterResults(); - filterResults.values = suggestions; - filterResults.count = suggestions.size(); - return filterResults; - } else { - return new FilterResults(); - } - } + @Override + protected FilterResults performFiltering(CharSequence constraint) { + if (constraint != null) { + ArrayList suggestions = new ArrayList<>(); + final String[] split = constraint.toString().split("@"); + if (split.length == 1) { + for (String domain : domains) { + suggestions.add(split[0].toLowerCase(Locale + .getDefault()) + "@" + domain); + } + } else if (split.length == 2) { + for (String domain : domains) { + if (domain.contentEquals(split[1])) { + suggestions.clear(); + break; + } else if (domain.contains(split[1])) { + suggestions.add(split[0].toLowerCase(Locale + .getDefault()) + "@" + domain); + } + } + } else { + return new FilterResults(); + } + FilterResults filterResults = new FilterResults(); + filterResults.values = suggestions; + filterResults.count = suggestions.size(); + return filterResults; + } else { + return new FilterResults(); + } + } - @Override - protected void publishResults(CharSequence constraint, - FilterResults results) { - ArrayList filteredList = (ArrayList) results.values; - if (results != null && results.count > 0) { - clear(); - for (Object c : filteredList) { - add((String) c); - } - notifyDataSetChanged(); - } - } - }; + @Override + protected void publishResults(CharSequence constraint, + FilterResults results) { + ArrayList filteredList = (ArrayList) results.values; + if (results != null && results.count > 0) { + clear(); + for (Object c : filteredList) { + add((String) c); + } + notifyDataSetChanged(); + } + } + }; - public KnownHostsAdapter(Context context, int viewResourceId, List mKnownHosts) { - super(context, viewResourceId, new ArrayList()); - domains = new ArrayList<>(mKnownHosts); - } + public KnownHostsAdapter(Context context, int viewResourceId, List mKnownHosts) { + super(context, viewResourceId, new ArrayList()); + domains = new ArrayList<>(mKnownHosts); + } - @Override - public Filter getFilter() { - return domainFilter; - } + @Override + public Filter getFilter() { + return domainFilter; + } } diff --git a/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java index 691b901db..1502b187d 100644 --- a/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java +++ b/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java @@ -28,156 +28,156 @@ import de.pixart.messenger.utils.UIHelper; public class ListItemAdapter extends ArrayAdapter { - protected XmppActivity activity; - protected boolean showDynamicTags = false; - private View.OnClickListener onTagTvClick = new View.OnClickListener() { - @Override - public void onClick(View view) { - if (view instanceof TextView && mOnTagClickedListener != null) { - TextView tv = (TextView) view; - final String tag = tv.getText().toString(); - mOnTagClickedListener.onTagClicked(tag); - } - } - }; - private OnTagClickedListener mOnTagClickedListener = null; - - public ListItemAdapter(XmppActivity activity, List objects) { - super(activity, 0, objects); - this.activity = activity; - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); - this.showDynamicTags = preferences.getBoolean("show_dynamic_tags",false); - } - - @Override - public View getView(int position, View view, ViewGroup parent) { - LayoutInflater inflater = (LayoutInflater) getContext() - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - ListItem item = getItem(position); - if (view == null) { - view = inflater.inflate(R.layout.contact, parent, false); - } - TextView tvName = (TextView) view.findViewById(R.id.contact_display_name); - TextView tvJid = (TextView) view.findViewById(R.id.contact_jid); - ImageView picture = (ImageView) view.findViewById(R.id.contact_photo); - FlowLayout tagLayout = (FlowLayout) view.findViewById(R.id.tags); - - List tags = item.getTags(activity); - if (tags.size() == 0 || !this.showDynamicTags) { - tagLayout.setVisibility(View.GONE); - } else { - tagLayout.setVisibility(View.VISIBLE); - tagLayout.removeAllViewsInLayout(); - for(ListItem.Tag tag : tags) { - TextView tv = (TextView) inflater.inflate(R.layout.list_item_tag,tagLayout,false); - tv.setText(tag.getName()); - tv.setBackgroundColor(tag.getColor()); - tv.setOnClickListener(this.onTagTvClick); - tagLayout.addView(tv); - } - } - final String jid = item.getDisplayJid(); - if (jid != null) { - tvJid.setVisibility(View.VISIBLE); - tvJid.setText(jid); - } else { - tvJid.setVisibility(View.GONE); - } - tvName.setText(item.getDisplayName()); - loadAvatar(item,picture); - return view; - } - - public void setOnTagClickedListener(OnTagClickedListener listener) { - this.mOnTagClickedListener = listener; - } - - public interface OnTagClickedListener { - void onTagClicked(String tag); - } - - class BitmapWorkerTask extends AsyncTask { - private final WeakReference imageViewReference; - private ListItem item = null; - - public BitmapWorkerTask(ImageView imageView) { - imageViewReference = new WeakReference<>(imageView); - } - - @Override - protected Bitmap doInBackground(ListItem... params) { - return activity.avatarService().get(params[0], activity.getPixel(48), isCancelled()); - } - - @Override - protected void onPostExecute(Bitmap bitmap) { - if (bitmap != null && !isCancelled()) { - final ImageView imageView = imageViewReference.get(); - if (imageView != null) { - imageView.setImageBitmap(bitmap); - imageView.setBackgroundColor(0x00000000); - } - } - } - } - - public void loadAvatar(ListItem item, ImageView imageView) { - if (cancelPotentialWork(item, imageView)) { - final Bitmap bm = activity.avatarService().get(item,activity.getPixel(48),true); - if (bm != null) { - cancelPotentialWork(item, imageView); - imageView.setImageBitmap(bm); - imageView.setBackgroundColor(0x00000000); - } else { - imageView.setBackgroundColor(UIHelper.getColorForName(item.getDisplayName())); - imageView.setImageDrawable(null); - final BitmapWorkerTask task = new BitmapWorkerTask(imageView); - final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task); - imageView.setImageDrawable(asyncDrawable); - try { - task.execute(item); - } catch (final RejectedExecutionException ignored) { - } - } - } - } - - public static boolean cancelPotentialWork(ListItem item, ImageView imageView) { - final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); - - if (bitmapWorkerTask != null) { - final ListItem oldItem = bitmapWorkerTask.item; - if (oldItem == null || item != oldItem) { - bitmapWorkerTask.cancel(true); - } else { - return false; - } - } - return true; - } - - private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { - if (imageView != null) { - final Drawable drawable = imageView.getDrawable(); - if (drawable instanceof AsyncDrawable) { - final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; - return asyncDrawable.getBitmapWorkerTask(); - } - } - return null; - } - - static class AsyncDrawable extends BitmapDrawable { - private final WeakReference bitmapWorkerTaskReference; - - public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) { - super(res, bitmap); - bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask); - } - - public BitmapWorkerTask getBitmapWorkerTask() { - return bitmapWorkerTaskReference.get(); - } - } + protected XmppActivity activity; + protected boolean showDynamicTags = false; + private View.OnClickListener onTagTvClick = new View.OnClickListener() { + @Override + public void onClick(View view) { + if (view instanceof TextView && mOnTagClickedListener != null) { + TextView tv = (TextView) view; + final String tag = tv.getText().toString(); + mOnTagClickedListener.onTagClicked(tag); + } + } + }; + private OnTagClickedListener mOnTagClickedListener = null; + + public ListItemAdapter(XmppActivity activity, List objects) { + super(activity, 0, objects); + this.activity = activity; + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); + this.showDynamicTags = preferences.getBoolean("show_dynamic_tags", false); + } + + @Override + public View getView(int position, View view, ViewGroup parent) { + LayoutInflater inflater = (LayoutInflater) getContext() + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + ListItem item = getItem(position); + if (view == null) { + view = inflater.inflate(R.layout.contact, parent, false); + } + TextView tvName = (TextView) view.findViewById(R.id.contact_display_name); + TextView tvJid = (TextView) view.findViewById(R.id.contact_jid); + ImageView picture = (ImageView) view.findViewById(R.id.contact_photo); + FlowLayout tagLayout = (FlowLayout) view.findViewById(R.id.tags); + + List tags = item.getTags(activity); + if (tags.size() == 0 || !this.showDynamicTags) { + tagLayout.setVisibility(View.GONE); + } else { + tagLayout.setVisibility(View.VISIBLE); + tagLayout.removeAllViewsInLayout(); + for (ListItem.Tag tag : tags) { + TextView tv = (TextView) inflater.inflate(R.layout.list_item_tag, tagLayout, false); + tv.setText(tag.getName()); + tv.setBackgroundColor(tag.getColor()); + tv.setOnClickListener(this.onTagTvClick); + tagLayout.addView(tv); + } + } + final String jid = item.getDisplayJid(); + if (jid != null) { + tvJid.setVisibility(View.VISIBLE); + tvJid.setText(jid); + } else { + tvJid.setVisibility(View.GONE); + } + tvName.setText(item.getDisplayName()); + loadAvatar(item, picture); + return view; + } + + public void setOnTagClickedListener(OnTagClickedListener listener) { + this.mOnTagClickedListener = listener; + } + + public interface OnTagClickedListener { + void onTagClicked(String tag); + } + + class BitmapWorkerTask extends AsyncTask { + private final WeakReference imageViewReference; + private ListItem item = null; + + public BitmapWorkerTask(ImageView imageView) { + imageViewReference = new WeakReference<>(imageView); + } + + @Override + protected Bitmap doInBackground(ListItem... params) { + return activity.avatarService().get(params[0], activity.getPixel(48), isCancelled()); + } + + @Override + protected void onPostExecute(Bitmap bitmap) { + if (bitmap != null && !isCancelled()) { + final ImageView imageView = imageViewReference.get(); + if (imageView != null) { + imageView.setImageBitmap(bitmap); + imageView.setBackgroundColor(0x00000000); + } + } + } + } + + public void loadAvatar(ListItem item, ImageView imageView) { + if (cancelPotentialWork(item, imageView)) { + final Bitmap bm = activity.avatarService().get(item, activity.getPixel(48), true); + if (bm != null) { + cancelPotentialWork(item, imageView); + imageView.setImageBitmap(bm); + imageView.setBackgroundColor(0x00000000); + } else { + imageView.setBackgroundColor(UIHelper.getColorForName(item.getDisplayName())); + imageView.setImageDrawable(null); + final BitmapWorkerTask task = new BitmapWorkerTask(imageView); + final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task); + imageView.setImageDrawable(asyncDrawable); + try { + task.execute(item); + } catch (final RejectedExecutionException ignored) { + } + } + } + } + + public static boolean cancelPotentialWork(ListItem item, ImageView imageView) { + final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); + + if (bitmapWorkerTask != null) { + final ListItem oldItem = bitmapWorkerTask.item; + if (oldItem == null || item != oldItem) { + bitmapWorkerTask.cancel(true); + } else { + return false; + } + } + return true; + } + + private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { + if (imageView != null) { + final Drawable drawable = imageView.getDrawable(); + if (drawable instanceof AsyncDrawable) { + final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; + return asyncDrawable.getBitmapWorkerTask(); + } + } + return null; + } + + static class AsyncDrawable extends BitmapDrawable { + private final WeakReference bitmapWorkerTaskReference; + + public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) { + super(res, bitmap); + bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask); + } + + public BitmapWorkerTask getBitmapWorkerTask() { + return bitmapWorkerTaskReference.get(); + } + } } diff --git a/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java index 02cbbdfd0..6cf16fdd5 100644 --- a/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java +++ b/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java @@ -74,941 +74,941 @@ import nl.changer.audiowife.AudioWife; public class MessageAdapter extends ArrayAdapter implements CopyTextView.CopyHandler { - private static final int SENT = 0; - private static final int RECEIVED = 1; - private static final int STATUS = 2; - private static final Pattern XMPP_PATTERN = Pattern - .compile("xmpp\\:(?:(?:[" - + Patterns.GOOD_IRI_CHAR - + "\\;\\/\\?\\@\\&\\=\\#\\~\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])" - + "|(?:\\%[a-fA-F0-9]{2}))+"); - - private ConversationActivity activity; - - private DisplayMetrics metrics; - - private AudioWife audioWife; - - private OnContactPictureClicked mOnContactPictureClickedListener; - private OnContactPictureLongClicked mOnContactPictureLongClickedListener; - - private boolean mIndicateReceived = false; - private final ListSelectionManager listSelectionManager = new ListSelectionManager(); - private HashMap audioPlayer; - private boolean mUseWhiteBackground = false; - - public MessageAdapter(ConversationActivity activity, List messages) { - super(activity, 0, messages); - this.activity = activity; - metrics = getContext().getResources().getDisplayMetrics(); - updatePreferences(); - } - - public void setOnContactPictureClicked(OnContactPictureClicked listener) { - this.mOnContactPictureClickedListener = listener; - } - - public void setOnContactPictureLongClicked( - OnContactPictureLongClicked listener) { - this.mOnContactPictureLongClickedListener = listener; - } - - @Override - public int getViewTypeCount() { - return 3; - } - - public int getItemViewType(Message message) { - if (message.getType() == Message.TYPE_STATUS) { - return STATUS; - } else if (message.getStatus() <= Message.STATUS_RECEIVED) { - return RECEIVED; - } - - return SENT; - } - - @Override - public int getItemViewType(int position) { - return getItemViewType(getItem(position)); - } - - private int getMessageTextColor(boolean onDark, boolean primary) { - if (onDark) { - return activity.getResources().getColor(primary ? R.color.dark : R.color.primary); - } else { - return activity.getResources().getColor(primary ? R.color.dark : R.color.primary); - } - } - - private void displayStatus(ViewHolder viewHolder, Message message, int type, boolean darkBackground) { - String filesize = null; - String info = null; - boolean error = false; - if (viewHolder.indicatorReceived != null) { - viewHolder.indicatorReceived.setVisibility(View.GONE); - viewHolder.indicatorRead.setVisibility(View.GONE); - } - - if (viewHolder.edit_indicator != null) { - if (message.edited()) { - viewHolder.edit_indicator.setVisibility(View.VISIBLE); - } else { - viewHolder.edit_indicator.setVisibility(View.GONE); - } - } - boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI - && message.getMergedStatus() <= Message.STATUS_RECEIVED; - if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || message.getTransferable() != null) { - FileParams params = message.getFileParams(); - if (params.size > (1 * 1024 * 1024)) { - filesize = params.size / (1024 * 1024) + " MiB"; - } else if (params.size > (1 * 1024)) { - filesize = params.size / 1024 + " KiB"; - } else if (params.size > 0) { - filesize = "1 KiB"; - } - if (message.getTransferable() != null && message.getTransferable().getStatus() == Transferable.STATUS_FAILED) { - error = true; - } - } - switch (message.getMergedStatus()) { - case Message.STATUS_WAITING: - info = getContext().getString(R.string.waiting); - break; - case Message.STATUS_UNSEND: - Transferable d = message.getTransferable(); - if (d != null) { - info = getContext().getString(R.string.sending_file, d.getProgress()); - } else { - info = getContext().getString(R.string.sending); - } - break; - case Message.STATUS_OFFERED: - info = getContext().getString(R.string.offering); - break; - case Message.STATUS_SEND_RECEIVED: - if (mIndicateReceived) { - viewHolder.indicatorReceived.setVisibility(View.VISIBLE); - } - break; - case Message.STATUS_SEND_DISPLAYED: - if (mIndicateReceived) { - viewHolder.indicatorReceived.setVisibility(View.VISIBLE); - viewHolder.indicatorRead.setVisibility(View.VISIBLE); - } - break; - case Message.STATUS_SEND_FAILED: - info = getContext().getString(R.string.send_failed); - error = true; - break; - default: - if (multiReceived) { - info = UIHelper.getMessageDisplayName(message); - } - break; - } - if (error && type == SENT) { - viewHolder.time.setTextColor(activity.getWarningTextColor()); - } else if (!message.isValidInSession() && type == RECEIVED) { - viewHolder.time.setTextColor(activity.getUnencryptedTextColor()); - } else { - viewHolder.time.setTextColor(this.getMessageTextColor(darkBackground,false)); - } - if (message.getEncryption() == Message.ENCRYPTION_NONE) { - viewHolder.indicator.setVisibility(View.GONE); - } else { - viewHolder.indicator.setVisibility(View.VISIBLE); - if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { - FingerprintStatus status = message.getConversation() - .getAccount().getAxolotlService().getFingerprintTrust( - message.getFingerprint()); - - if(status == null || (!status.isTrustedAndActive())) { - viewHolder.indicator.setColorFilter(activity.getWarningTextColor()); - viewHolder.indicator.setAlpha(1.0f); - } else { - viewHolder.indicator.clearColorFilter(); - if (darkBackground) { - viewHolder.indicator.setAlpha(0.7f); - } else { - viewHolder.indicator.setAlpha(0.57f); - } - } - } else { - viewHolder.indicator.clearColorFilter(); - if (darkBackground) { - viewHolder.indicator.setAlpha(0.7f); - } else { - viewHolder.indicator.setAlpha(0.57f); - } - } - } - - String formatedTime = UIHelper.readableTimeDifferenceFull(getContext(), - message.getMergedTimeSent()); - if (message.getStatus() <= Message.STATUS_RECEIVED) { - if ((filesize != null) && (info != null)) { - viewHolder.time.setText(formatedTime + " \u00B7 " + filesize +" \u00B7 " + info); - } else if ((filesize == null) && (info != null)) { - viewHolder.time.setText(formatedTime + " \u00B7 " + info); - } else if ((filesize != null) && (info == null)) { - viewHolder.time.setText(formatedTime + " \u00B7 " + filesize); - } else { - viewHolder.time.setText(formatedTime); - } - } else { - if ((filesize != null) && (info != null)) { - viewHolder.time.setText(filesize + " \u00B7 " + info); - } else if ((filesize == null) && (info != null)) { - if (error) { - viewHolder.time.setText(info + " \u00B7 " + formatedTime); - } else { - viewHolder.time.setText(info); - } - } else if ((filesize != null) && (info == null)) { - viewHolder.time.setText(filesize + " \u00B7 " + formatedTime); - } else { - viewHolder.time.setText(formatedTime); - } - } - } - - private void displayInfoMessage(ViewHolder viewHolder, String text, boolean darkBackground) { - viewHolder.aw_player.setVisibility(View.GONE); - if (viewHolder.download_button != null) { - viewHolder.download_button.setVisibility(View.GONE); - } - viewHolder.image.setVisibility(View.GONE); - viewHolder.messageBody.setVisibility(View.VISIBLE); - viewHolder.messageBody.setText(text); - viewHolder.messageBody.setTextColor(getMessageTextColor(darkBackground, false)); - viewHolder.messageBody.setTypeface(null, Typeface.ITALIC); - viewHolder.messageBody.setTextIsSelectable(false); - } - - private void displayDecryptionFailed(ViewHolder viewHolder, boolean darkBackground) { - viewHolder.aw_player.setVisibility(View.GONE); - if (viewHolder.download_button != null) { - viewHolder.download_button.setVisibility(View.GONE); - } - viewHolder.image.setVisibility(View.GONE); - viewHolder.messageBody.setVisibility(View.VISIBLE); - viewHolder.messageBody.setText(getContext().getString( - R.string.decryption_failed)); - viewHolder.messageBody.setTextColor(getMessageTextColor(darkBackground, false)); - viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); - viewHolder.messageBody.setTextIsSelectable(false); - } - - private void displayHeartMessage(final ViewHolder viewHolder, final String body) { - viewHolder.aw_player.setVisibility(View.GONE); - if (viewHolder.download_button != null) { - viewHolder.download_button.setVisibility(View.GONE); - } - viewHolder.image.setVisibility(View.GONE); - viewHolder.messageBody.setVisibility(View.VISIBLE); - viewHolder.messageBody.setIncludeFontPadding(false); - Spannable span = new SpannableString(body); - span.setSpan(new RelativeSizeSpan(4.0f), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - span.setSpan(new ForegroundColorSpan(activity.getWarningTextColor()), 0, body.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - viewHolder.messageBody.setText(span); - } - - private void displayXmppMessage(final ViewHolder viewHolder, final String body) { - String contact = body.toLowerCase(); - contact = contact.split(":")[1]; - contact = contact.split("\\?")[0]; - String add_contact = activity.getString(R.string.add_to_contact_list) + " (" + contact + ")"; - viewHolder.aw_player.setVisibility(View.GONE); - viewHolder.download_button.setVisibility(View.VISIBLE); - viewHolder.download_button.setText(add_contact); - viewHolder.download_button.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(body)); - activity.startActivity(intent); - } - }); - viewHolder.image.setVisibility(View.GONE); - viewHolder.messageBody.setVisibility(View.GONE); - - } - - private void displayTextMessage(final ViewHolder viewHolder, final Message message, boolean darkBackground) { - if (viewHolder.download_button != null) { - viewHolder.download_button.setVisibility(View.GONE); - } - viewHolder.image.setVisibility(View.GONE); - viewHolder.messageBody.setVisibility(View.VISIBLE); - viewHolder.messageBody.setIncludeFontPadding(true); - if (message.getBody() != null) { - final String nick = UIHelper.getMessageDisplayName(message); - SpannableStringBuilder body = message.getMergedBody(); - boolean hasMeCommand = message.hasMeCommand(); - if (hasMeCommand) { - body = body.replace(0, Message.ME_COMMAND.length(), nick + " "); - } - if (body.length() > Config.MAX_DISPLAY_MESSAGE_CHARS) { - body = new SpannableStringBuilder(body, 0, Config.MAX_DISPLAY_MESSAGE_CHARS); - body.append("\u2026"); - } - Message.MergeSeparator[] mergeSeparators = body.getSpans(0, body.length(), Message.MergeSeparator.class); - for (Message.MergeSeparator mergeSeparator : mergeSeparators) { - int start = body.getSpanStart(mergeSeparator); - int end = body.getSpanEnd(mergeSeparator); - body.setSpan(new RelativeSizeSpan(0.3f), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - if (message.getType() != Message.TYPE_PRIVATE) { - if (hasMeCommand) { - body.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 0, nick.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } else { - String privateMarker; - if (message.getStatus() <= Message.STATUS_RECEIVED) { - privateMarker = activity - .getString(R.string.private_message); - } else { - final String to; - if (message.getCounterpart() != null) { - to = message.getCounterpart().getResourcepart(); - } else { - to = ""; - } - privateMarker = activity.getString(R.string.private_message_to, to); - } - body.insert(0, privateMarker); - int privateMarkerIndex = privateMarker.length(); - body.insert(privateMarkerIndex, " "); - body.setSpan(new ForegroundColorSpan(getMessageTextColor(darkBackground, false)), 0, privateMarkerIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - body.setSpan(new StyleSpan(Typeface.BOLD), 0, privateMarkerIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - if (hasMeCommand) { - body.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), privateMarkerIndex + 1, privateMarkerIndex + 1 + nick.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } - Linkify.addLinks(body, Linkify.WEB_URLS); - Linkify.addLinks(body, XMPP_PATTERN, "xmpp"); - Linkify.addLinks(body, GeoHelper.GEO_URI, "geo"); - viewHolder.messageBody.setAutoLinkMask(0); - viewHolder.messageBody.setText(body); - viewHolder.messageBody.setTextIsSelectable(true); - viewHolder.messageBody.setMovementMethod(ClickableMovementMethod.getInstance()); - listSelectionManager.onUpdate(viewHolder.messageBody, message); - - } else { - viewHolder.messageBody.setText(""); - viewHolder.messageBody.setTextIsSelectable(false); - } - viewHolder.messageBody.setTextColor(this.getMessageTextColor(darkBackground, true)); - viewHolder.messageBody.setLinkTextColor(this.getMessageTextColor(darkBackground, true)); - viewHolder.messageBody.setHighlightColor(activity.getResources().getColor(darkBackground ? R.color.grey800 : R.color.grey500)); - viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); - } - - private void displayDownloadableMessage(ViewHolder viewHolder, final Message message, String text) { - viewHolder.aw_player.setVisibility(View.GONE); - viewHolder.image.setVisibility(View.GONE); - viewHolder.messageBody.setVisibility(View.GONE); - viewHolder.download_button.setVisibility(View.VISIBLE); - viewHolder.download_button.setText(text); - viewHolder.download_button.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_download_grey600_48dp,0,0,0); - viewHolder.download_button.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - activity.startDownloadable(message); - } - }); - } - - private void displayAudioMessage(ViewHolder viewHolder, final Message message, int position) { - if (audioPlayer == null) audioPlayer = new HashMap<>(); - viewHolder.image.setVisibility(View.GONE); - viewHolder.messageBody.setVisibility(View.GONE); - if (viewHolder.download_button != null) { - viewHolder.download_button.setVisibility(View.GONE); - } - viewHolder.aw_player.setVisibility(View.VISIBLE); - Uri audioFile = Uri.fromFile(activity.xmppConnectionService.getFileBackend().getFile(message)); - - audioWife = audioPlayer.get(position); - if (audioWife == null) { - audioWife = new AudioWife(); - audioWife.init(getContext(), audioFile); - audioPlayer.put(position, audioWife); - RelativeLayout vg = new RelativeLayout(activity); - LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); - audioWife.useDefaultUi(vg, layoutInflater); - viewHolder.aw_player.addView(audioWife.getPlayerUi()); - } else { - audioWife.cleanPlayerUi(); - viewHolder.aw_player.addView(audioWife.getPlayerUi()); - } - } - - private void displayOpenableMessage(ViewHolder viewHolder,final Message message) { - viewHolder.aw_player.setVisibility(View.GONE); - viewHolder.image.setVisibility(View.GONE); - viewHolder.messageBody.setVisibility(View.GONE); - viewHolder.download_button.setVisibility(View.VISIBLE); - String mimeType = message.getMimeType(); - String fullName = ""; - if (mimeType != null) { - if (message.getMimeType().contains("pdf")) { - viewHolder.download_button.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_file_pdf_grey600_48dp,0,0,0); - } else if (message.getMimeType().contains("vcard")) { - File file = new File(activity.xmppConnectionService.getFileBackend().getFile(message).toString()); - VCard vcard = null; - String name = null; - String version = null; - try { - vcard = Ezvcard.parse(file).first(); - } catch (IOException e) { - e.printStackTrace(); - } - if (vcard != null) { - version = vcard.getVersion().toString(); - Log.d(Config.LOGTAG, "VCard version: " + version); - name = vcard.getFormattedName().getValue(); - fullName = " (" + name + ")"; - } - viewHolder.download_button.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_account_card_details_grey600_48dp,0,0,0); - } else if (message.getMimeType().contains("calendar")) { - viewHolder.download_button.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_calendar_grey600_48dp,0,0,0); - } else { - viewHolder.download_button.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_file_grey600_48dp,0,0,0); - } - } - viewHolder.download_button.setText(activity.getString(R.string.open_x_file, UIHelper.getFileDescriptionString(activity, message) + fullName)); - viewHolder.download_button.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - openDownloadable(message); - } - }); - } - - private void displayLocationMessage(ViewHolder viewHolder, final Message message) { - viewHolder.aw_player.setVisibility(View.GONE); - viewHolder.messageBody.setVisibility(View.GONE); - String url = GeoHelper.MapPreviewUri(message); - viewHolder.image.setVisibility(View.VISIBLE); - viewHolder.image.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - showLocation(message); - } - }); - Glide - .with(activity) - .load(Uri.parse(url)) - .asBitmap() - .diskCacheStrategy(DiskCacheStrategy.ALL) - .fitCenter() - .placeholder(R.drawable.ic_map_marker_grey600_48dp) - .error(R.drawable.ic_map_marker_grey600_48dp) - .into(viewHolder.image); - viewHolder.image.setMaxWidth(500); - viewHolder.image.setAdjustViewBounds(true); - viewHolder.download_button.setVisibility(View.GONE); - viewHolder.download_button.setText(R.string.show_location); - viewHolder.download_button.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_map_marker_grey600_48dp, 0, 0, 0); - viewHolder.download_button.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - showLocation(message); - } - }); - - } - - private void displayImageMessage(ViewHolder viewHolder, - final Message message) { - viewHolder.aw_player.setVisibility(View.GONE); - if (viewHolder.download_button != null) { - viewHolder.download_button.setVisibility(View.GONE); - } - viewHolder.messageBody.setVisibility(View.GONE); - viewHolder.image.setVisibility(View.VISIBLE); - FileParams params = message.getFileParams(); - double target = metrics.density * 200; - int scaledW; - int scaledH; - if (Math.max(params.height, params.width) * metrics.density <= target) { - scaledW = (int) (params.width * metrics.density); - scaledH = (int) (params.height * metrics.density); - } else if (Math.max(params.height,params.width) <= target) { - scaledW = params.width; - scaledH = params.height; - } else if (params.width <= params.height) { - scaledW = (int) (params.width / ((double) params.height / target)); - scaledH = (int) target; - } else { - scaledW = (int) target; - scaledH = (int) (params.height / ((double) params.width / target)); - } - LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(scaledW, scaledH); - layoutParams.setMargins(0, (int) (metrics.density * 4), 0, (int) (metrics.density * 4)); - viewHolder.image.setLayoutParams(layoutParams); - activity.loadBitmap(message, viewHolder.image); - viewHolder.image.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - openDownloadable(message); - } - }); - } - - private void loadMoreMessages(Conversation conversation) { - conversation.setLastClearHistory(0); - activity.xmppConnectionService.updateConversation(conversation); - conversation.setHasMessagesLeftOnServer(true); - conversation.setFirstMamReference(null); - long timestamp = conversation.getLastMessageTransmitted(); - if (timestamp == 0) { - timestamp = System.currentTimeMillis(); - } - activity.setMessagesLoaded(); - activity.xmppConnectionService.getMessageArchiveService().query(conversation, 0, timestamp); - Toast.makeText(activity, R.string.fetching_history_from_server,Toast.LENGTH_LONG).show(); - } - - @Override - public View getView(int position, View unused, ViewGroup parent) { - final Message message = getItem(position); - final boolean isInValidSession = message.isValidInSession(); - final Conversation conversation = message.getConversation(); - final Account account = conversation.getAccount(); - final int type = getItemViewType(position); - ViewHolder viewHolder; - View view; - viewHolder = new ViewHolder(); - switch (type) { - case SENT: - view = activity.getLayoutInflater().inflate( - R.layout.message_sent, parent, false); - viewHolder.message_box = (LinearLayout) view - .findViewById(R.id.message_box); - viewHolder.contact_picture = (ImageView) view - .findViewById(R.id.message_photo); - viewHolder.aw_player = (ViewGroup) view.findViewById(R.id.aw_player); - viewHolder.download_button = (Button) view - .findViewById(R.id.download_button); - viewHolder.indicator = (ImageView) view - .findViewById(R.id.security_indicator); - viewHolder.edit_indicator = (ImageView) view.findViewById(R.id.edit_indicator); - viewHolder.image = (ImageView) view - .findViewById(R.id.message_image); - viewHolder.messageBody = (CopyTextView) view - .findViewById(R.id.message_body); - viewHolder.time = (TextView) view - .findViewById(R.id.message_time); - viewHolder.indicatorReceived = (ImageView) view - .findViewById(R.id.indicator_received); - viewHolder.indicatorRead = (ImageView) view - .findViewById(R.id.indicator_read); - break; - case RECEIVED: - view = activity.getLayoutInflater().inflate( - R.layout.message_received, parent, false); - viewHolder.message_box = (LinearLayout) view - .findViewById(R.id.message_box); - viewHolder.contact_picture = (ImageView) view - .findViewById(R.id.message_photo); - viewHolder.aw_player = (ViewGroup) view.findViewById(R.id.aw_player); - viewHolder.download_button = (Button) view - .findViewById(R.id.download_button); - viewHolder.indicator = (ImageView) view - .findViewById(R.id.security_indicator); - viewHolder.edit_indicator = (ImageView) view.findViewById(R.id.edit_indicator); - viewHolder.image = (ImageView) view - .findViewById(R.id.message_image); - viewHolder.messageBody = (CopyTextView) view - .findViewById(R.id.message_body); - viewHolder.time = (TextView) view - .findViewById(R.id.message_time); - viewHolder.indicatorReceived = (ImageView) view - .findViewById(R.id.indicator_received); - viewHolder.encryption = (TextView) view.findViewById(R.id.message_encryption); - break; - case STATUS: - view = activity.getLayoutInflater().inflate(R.layout.message_status, parent, false); - viewHolder.contact_picture = (ImageView) view.findViewById(R.id.message_photo); - viewHolder.status_message = (TextView) view.findViewById(R.id.status_message); - viewHolder.load_more_messages = (Button) view.findViewById(R.id.load_more_messages); - break; - default: - view = new View(getContext()); - viewHolder = null; - break; - } - if (viewHolder.messageBody != null) { - listSelectionManager.onCreate(viewHolder.messageBody); - viewHolder.messageBody.setCopyHandler(this); - } - view.setTag(viewHolder); - if (viewHolder == null) { - return view; - } - - - boolean darkBackground = (type == SENT && (!isInValidSession || !mUseWhiteBackground)); - - if (type == STATUS) { - if ("LOAD_MORE".equals(message.getBody())) { - viewHolder.status_message.setVisibility(View.GONE); - viewHolder.contact_picture.setVisibility(View.GONE); - viewHolder.load_more_messages.setVisibility(View.VISIBLE); - viewHolder.load_more_messages.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - loadMoreMessages(message.getConversation()); - } - }); - } else { - viewHolder.status_message.setVisibility(View.VISIBLE); - viewHolder.contact_picture.setVisibility(View.VISIBLE); - viewHolder.load_more_messages.setVisibility(View.GONE); - if (conversation.getMode() == Conversation.MODE_SINGLE) { - viewHolder.contact_picture.setImageBitmap(activity - .avatarService().get(conversation.getContact(), - activity.getPixel(32))); - viewHolder.contact_picture.setAlpha(0.5f); - } - viewHolder.status_message.setText(message.getBody()); - } - return view; - } else { - loadAvatar(message, viewHolder.contact_picture); - } - - viewHolder.contact_picture - .setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - if (MessageAdapter.this.mOnContactPictureClickedListener != null) { - MessageAdapter.this.mOnContactPictureClickedListener - .onContactPictureClicked(message); - } - - } - }); - viewHolder.contact_picture - .setOnLongClickListener(new OnLongClickListener() { - - @Override - public boolean onLongClick(View v) { - if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) { - MessageAdapter.this.mOnContactPictureLongClickedListener - .onContactPictureLongClicked(message); - return true; - } else { - return false; - } - } - }); - - final Transferable transferable = message.getTransferable(); - String mimeType = message.getMimeType(); - if (transferable != null && transferable.getStatus() != Transferable.STATUS_UPLOADING) { - if (transferable.getStatus() == Transferable.STATUS_OFFER) { - displayDownloadableMessage(viewHolder,message,activity.getString(R.string.download_x_file, UIHelper.getFileDescriptionString(activity, message))); - } else if (transferable.getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE) { - displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message))); - } else { - displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first,darkBackground); - } - } else if (message.getType() == Message.TYPE_IMAGE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) { - displayImageMessage(viewHolder, message); - } else if (message.getType() == Message.TYPE_FILE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) { - if (message.getFileParams().width > 0) { - displayImageMessage(viewHolder,message); - } else { - if (mimeType != null) { - if (message.getMimeType().startsWith("audio/")) { - displayAudioMessage(viewHolder, message, position); - } else displayOpenableMessage(viewHolder, message); - } else displayOpenableMessage(viewHolder, message); - } - } else if (message.getEncryption() == Message.ENCRYPTION_PGP) { - if (account.isPgpDecryptionServiceConnected()) { - if (!account.hasPendingPgpIntent(conversation)) { - displayInfoMessage(viewHolder, activity.getString(R.string.message_decrypting), darkBackground); - } else { - displayInfoMessage(viewHolder, activity.getString(R.string.pgp_message), darkBackground); - } - } else { - displayInfoMessage(viewHolder,activity.getString(R.string.install_openkeychain),darkBackground); - if (viewHolder != null) { - viewHolder.message_box - .setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - activity.showInstallPgpDialog(); - } - }); - } - } - } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { - displayDecryptionFailed(viewHolder,darkBackground); - } else { - if (GeoHelper.isGeoUri(message.getBody())) { - displayLocationMessage(viewHolder,message); - } else if (message.bodyIsHeart()) { - displayHeartMessage(viewHolder, message.getBody().trim()); - } else if (message.bodyIsXmpp()) { - displayXmppMessage(viewHolder, message.getBody().trim()); - } else if (message.treatAsDownloadable() == Message.Decision.MUST || - message.treatAsDownloadable() == Message.Decision.SHOULD) { - try { - URL url = new URL(message.getBody()); - displayDownloadableMessage(viewHolder, - message, - activity.getString(R.string.check_x_filesize_on_host, - UIHelper.getFileDescriptionString(activity, message), - url.getHost())); - } catch (Exception e) { - displayDownloadableMessage(viewHolder, - message, - activity.getString(R.string.check_x_filesize, - UIHelper.getFileDescriptionString(activity, message))); - } - } else { - displayTextMessage(viewHolder, message, darkBackground); - } - } - - if (type == RECEIVED) { - if(isInValidSession) { - viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received); - viewHolder.encryption.setVisibility(View.GONE); - } else { - viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received_warning); - viewHolder.encryption.setVisibility(View.VISIBLE); - viewHolder.encryption.setText(CryptoHelper.encryptionTypeToText(message.getEncryption())); - } - } - - if (type == SENT) { - if (mUseWhiteBackground) { - viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_sent_white); - } else { - viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_sent); - } - } - - displayStatus(viewHolder, message, type, darkBackground); - - return view; - } - - @Override - public void notifyDataSetChanged() { - listSelectionManager.onBeforeNotifyDataSetChanged(); - super.notifyDataSetChanged(); - listSelectionManager.onAfterNotifyDataSetChanged(); - } - - @Override - public String transformTextForCopy(CharSequence text, int start, int end) { - return text.toString().substring(start, end); - } - - public void openDownloadable(Message message) { - DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message); - if (!file.exists()) { - Toast.makeText(activity, R.string.file_deleted, Toast.LENGTH_SHORT).show(); - return; - } - String mime = file.getMimeType(); - if (mime.startsWith("image/")) { - Intent intent = new Intent(getContext(), ShowFullscreenMessageActivity.class); - intent.putExtra("image", Uri.fromFile(file)); - try { - activity.startActivity(intent); - return; - } catch (ActivityNotFoundException e) { - //ignored - } - } else if (mime.startsWith("video/")) { - Intent intent = new Intent(getContext(), ShowFullscreenMessageActivity.class); - intent.putExtra("video", Uri.fromFile(file)); - try { - activity.startActivity(intent); - return; - } catch (ActivityNotFoundException e) { - //ignored - } - } - Intent openIntent = new Intent(Intent.ACTION_VIEW); - if (mime == null) { - mime = "*/*"; - } - Uri uri; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - try { - uri = FileProvider.getUriForFile(activity, FileBackend.CONVERSATIONS_FILE_PROVIDER, file); - } catch (IllegalArgumentException e) { - Toast.makeText(activity,activity.getString(R.string.no_permission_to_access_x,file.getAbsolutePath()), Toast.LENGTH_SHORT).show(); - return; - } - openIntent.setDataAndType(uri, mime); - openIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - } else { - uri = Uri.fromFile(file); - } - openIntent.setDataAndType(uri, mime); - PackageManager manager = activity.getPackageManager(); - List info = manager.queryIntentActivities(openIntent, 0); - if (info.size() == 0) { - openIntent.setDataAndType(Uri.fromFile(file),"*/*"); - } - try { - getContext().startActivity(openIntent); - } catch (ActivityNotFoundException e) { - Toast.makeText(activity,R.string.no_application_found_to_open_file,Toast.LENGTH_SHORT).show(); - } - } - - public void showLocation(Message message) { - for(Intent intent : GeoHelper.createGeoIntentsFromMessage(message)) { - if (intent.resolveActivity(getContext().getPackageManager()) != null) { - getContext().startActivity(intent); - return; - } - } - Toast.makeText(activity,R.string.no_application_found_to_display_location,Toast.LENGTH_SHORT).show(); - } - - public void updatePreferences() { - this.mIndicateReceived = activity.indicateReceived(); - this.mUseWhiteBackground = activity.useWhiteBackground(); - } - - public TextView getMessageBody(View view) { - final Object tag = view.getTag(); - if (tag instanceof ViewHolder) { - final ViewHolder viewHolder = (ViewHolder) tag; - return viewHolder.messageBody; - } - return null; - } - - public interface OnContactPictureClicked { - void onContactPictureClicked(Message message); - } - - public interface OnContactPictureLongClicked { - void onContactPictureLongClicked(Message message); - } - - private static class ViewHolder { - - protected LinearLayout message_box; - protected Button download_button; - protected ViewGroup aw_player; - protected ImageView image; - protected ImageView indicator; - protected ImageView indicatorReceived; - protected ImageView indicatorRead; - protected TextView time; - protected CopyTextView messageBody; - protected ImageView contact_picture; - protected TextView status_message; - protected TextView encryption; - public Button load_more_messages; - public ImageView edit_indicator; - } - - class BitmapWorkerTask extends AsyncTask { - private final WeakReference imageViewReference; - private Message message = null; - - public BitmapWorkerTask(ImageView imageView) { - imageViewReference = new WeakReference<>(imageView); - } - - @Override - protected Bitmap doInBackground(Message... params) { - return activity.avatarService().get(params[0], activity.getPixel(48), isCancelled()); - } - - @Override - protected void onPostExecute(Bitmap bitmap) { - if (bitmap != null && !isCancelled()) { - final ImageView imageView = imageViewReference.get(); - if (imageView != null) { - imageView.setImageBitmap(bitmap); - imageView.setBackgroundColor(0x00000000); - } - } - } - } - - public void loadAvatar(Message message, ImageView imageView) { - if (cancelPotentialWork(message, imageView)) { - final Bitmap bm = activity.avatarService().get(message, activity.getPixel(48), true); - if (bm != null) { - cancelPotentialWork(message, imageView); - imageView.setImageBitmap(bm); - imageView.setBackgroundColor(0x00000000); - } else { - imageView.setBackgroundColor(UIHelper.getColorForName(UIHelper.getMessageDisplayName(message))); - imageView.setImageDrawable(null); - final BitmapWorkerTask task = new BitmapWorkerTask(imageView); - final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task); - imageView.setImageDrawable(asyncDrawable); - try { - task.execute(message); - } catch (final RejectedExecutionException ignored) { - } - } - } - } - - public static boolean cancelPotentialWork(Message message, ImageView imageView) { - final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); - - if (bitmapWorkerTask != null) { - final Message oldMessage = bitmapWorkerTask.message; - if (oldMessage == null || message != oldMessage) { - bitmapWorkerTask.cancel(true); - } else { - return false; - } - } - return true; - } - - private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { - if (imageView != null) { - final Drawable drawable = imageView.getDrawable(); - if (drawable instanceof AsyncDrawable) { - final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; - return asyncDrawable.getBitmapWorkerTask(); - } - } - return null; - } - - static class AsyncDrawable extends BitmapDrawable { - private final WeakReference bitmapWorkerTaskReference; - - public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) { - super(res, bitmap); - bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask); - } - - public BitmapWorkerTask getBitmapWorkerTask() { - return bitmapWorkerTaskReference.get(); - } - } + private static final int SENT = 0; + private static final int RECEIVED = 1; + private static final int STATUS = 2; + private static final Pattern XMPP_PATTERN = Pattern + .compile("xmpp\\:(?:(?:[" + + Patterns.GOOD_IRI_CHAR + + "\\;\\/\\?\\@\\&\\=\\#\\~\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])" + + "|(?:\\%[a-fA-F0-9]{2}))+"); + + private ConversationActivity activity; + + private DisplayMetrics metrics; + + private AudioWife audioWife; + + private OnContactPictureClicked mOnContactPictureClickedListener; + private OnContactPictureLongClicked mOnContactPictureLongClickedListener; + + private boolean mIndicateReceived = false; + private final ListSelectionManager listSelectionManager = new ListSelectionManager(); + private HashMap audioPlayer; + private boolean mUseWhiteBackground = false; + + public MessageAdapter(ConversationActivity activity, List messages) { + super(activity, 0, messages); + this.activity = activity; + metrics = getContext().getResources().getDisplayMetrics(); + updatePreferences(); + } + + public void setOnContactPictureClicked(OnContactPictureClicked listener) { + this.mOnContactPictureClickedListener = listener; + } + + public void setOnContactPictureLongClicked( + OnContactPictureLongClicked listener) { + this.mOnContactPictureLongClickedListener = listener; + } + + @Override + public int getViewTypeCount() { + return 3; + } + + public int getItemViewType(Message message) { + if (message.getType() == Message.TYPE_STATUS) { + return STATUS; + } else if (message.getStatus() <= Message.STATUS_RECEIVED) { + return RECEIVED; + } + + return SENT; + } + + @Override + public int getItemViewType(int position) { + return getItemViewType(getItem(position)); + } + + private int getMessageTextColor(boolean onDark, boolean primary) { + if (onDark) { + return activity.getResources().getColor(primary ? R.color.dark : R.color.primary); + } else { + return activity.getResources().getColor(primary ? R.color.dark : R.color.primary); + } + } + + private void displayStatus(ViewHolder viewHolder, Message message, int type, boolean darkBackground) { + String filesize = null; + String info = null; + boolean error = false; + if (viewHolder.indicatorReceived != null) { + viewHolder.indicatorReceived.setVisibility(View.GONE); + viewHolder.indicatorRead.setVisibility(View.GONE); + } + + if (viewHolder.edit_indicator != null) { + if (message.edited()) { + viewHolder.edit_indicator.setVisibility(View.VISIBLE); + } else { + viewHolder.edit_indicator.setVisibility(View.GONE); + } + } + boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI + && message.getMergedStatus() <= Message.STATUS_RECEIVED; + if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || message.getTransferable() != null) { + FileParams params = message.getFileParams(); + if (params.size > (1 * 1024 * 1024)) { + filesize = params.size / (1024 * 1024) + " MiB"; + } else if (params.size > (1 * 1024)) { + filesize = params.size / 1024 + " KiB"; + } else if (params.size > 0) { + filesize = "1 KiB"; + } + if (message.getTransferable() != null && message.getTransferable().getStatus() == Transferable.STATUS_FAILED) { + error = true; + } + } + switch (message.getMergedStatus()) { + case Message.STATUS_WAITING: + info = getContext().getString(R.string.waiting); + break; + case Message.STATUS_UNSEND: + Transferable d = message.getTransferable(); + if (d != null) { + info = getContext().getString(R.string.sending_file, d.getProgress()); + } else { + info = getContext().getString(R.string.sending); + } + break; + case Message.STATUS_OFFERED: + info = getContext().getString(R.string.offering); + break; + case Message.STATUS_SEND_RECEIVED: + if (mIndicateReceived) { + viewHolder.indicatorReceived.setVisibility(View.VISIBLE); + } + break; + case Message.STATUS_SEND_DISPLAYED: + if (mIndicateReceived) { + viewHolder.indicatorReceived.setVisibility(View.VISIBLE); + viewHolder.indicatorRead.setVisibility(View.VISIBLE); + } + break; + case Message.STATUS_SEND_FAILED: + info = getContext().getString(R.string.send_failed); + error = true; + break; + default: + if (multiReceived) { + info = UIHelper.getMessageDisplayName(message); + } + break; + } + if (error && type == SENT) { + viewHolder.time.setTextColor(activity.getWarningTextColor()); + } else if (!message.isValidInSession() && type == RECEIVED) { + viewHolder.time.setTextColor(activity.getUnencryptedTextColor()); + } else { + viewHolder.time.setTextColor(this.getMessageTextColor(darkBackground, false)); + } + if (message.getEncryption() == Message.ENCRYPTION_NONE) { + viewHolder.indicator.setVisibility(View.GONE); + } else { + viewHolder.indicator.setVisibility(View.VISIBLE); + if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { + FingerprintStatus status = message.getConversation() + .getAccount().getAxolotlService().getFingerprintTrust( + message.getFingerprint()); + + if (status == null || (!status.isTrustedAndActive())) { + viewHolder.indicator.setColorFilter(activity.getWarningTextColor()); + viewHolder.indicator.setAlpha(1.0f); + } else { + viewHolder.indicator.clearColorFilter(); + if (darkBackground) { + viewHolder.indicator.setAlpha(0.7f); + } else { + viewHolder.indicator.setAlpha(0.57f); + } + } + } else { + viewHolder.indicator.clearColorFilter(); + if (darkBackground) { + viewHolder.indicator.setAlpha(0.7f); + } else { + viewHolder.indicator.setAlpha(0.57f); + } + } + } + + String formatedTime = UIHelper.readableTimeDifferenceFull(getContext(), + message.getMergedTimeSent()); + if (message.getStatus() <= Message.STATUS_RECEIVED) { + if ((filesize != null) && (info != null)) { + viewHolder.time.setText(formatedTime + " \u00B7 " + filesize + " \u00B7 " + info); + } else if ((filesize == null) && (info != null)) { + viewHolder.time.setText(formatedTime + " \u00B7 " + info); + } else if ((filesize != null) && (info == null)) { + viewHolder.time.setText(formatedTime + " \u00B7 " + filesize); + } else { + viewHolder.time.setText(formatedTime); + } + } else { + if ((filesize != null) && (info != null)) { + viewHolder.time.setText(filesize + " \u00B7 " + info); + } else if ((filesize == null) && (info != null)) { + if (error) { + viewHolder.time.setText(info + " \u00B7 " + formatedTime); + } else { + viewHolder.time.setText(info); + } + } else if ((filesize != null) && (info == null)) { + viewHolder.time.setText(filesize + " \u00B7 " + formatedTime); + } else { + viewHolder.time.setText(formatedTime); + } + } + } + + private void displayInfoMessage(ViewHolder viewHolder, String text, boolean darkBackground) { + viewHolder.aw_player.setVisibility(View.GONE); + if (viewHolder.download_button != null) { + viewHolder.download_button.setVisibility(View.GONE); + } + viewHolder.image.setVisibility(View.GONE); + viewHolder.messageBody.setVisibility(View.VISIBLE); + viewHolder.messageBody.setText(text); + viewHolder.messageBody.setTextColor(getMessageTextColor(darkBackground, false)); + viewHolder.messageBody.setTypeface(null, Typeface.ITALIC); + viewHolder.messageBody.setTextIsSelectable(false); + } + + private void displayDecryptionFailed(ViewHolder viewHolder, boolean darkBackground) { + viewHolder.aw_player.setVisibility(View.GONE); + if (viewHolder.download_button != null) { + viewHolder.download_button.setVisibility(View.GONE); + } + viewHolder.image.setVisibility(View.GONE); + viewHolder.messageBody.setVisibility(View.VISIBLE); + viewHolder.messageBody.setText(getContext().getString( + R.string.decryption_failed)); + viewHolder.messageBody.setTextColor(getMessageTextColor(darkBackground, false)); + viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); + viewHolder.messageBody.setTextIsSelectable(false); + } + + private void displayHeartMessage(final ViewHolder viewHolder, final String body) { + viewHolder.aw_player.setVisibility(View.GONE); + if (viewHolder.download_button != null) { + viewHolder.download_button.setVisibility(View.GONE); + } + viewHolder.image.setVisibility(View.GONE); + viewHolder.messageBody.setVisibility(View.VISIBLE); + viewHolder.messageBody.setIncludeFontPadding(false); + Spannable span = new SpannableString(body); + span.setSpan(new RelativeSizeSpan(4.0f), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + span.setSpan(new ForegroundColorSpan(activity.getWarningTextColor()), 0, body.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + viewHolder.messageBody.setText(span); + } + + private void displayXmppMessage(final ViewHolder viewHolder, final String body) { + String contact = body.toLowerCase(); + contact = contact.split(":")[1]; + contact = contact.split("\\?")[0]; + String add_contact = activity.getString(R.string.add_to_contact_list) + " (" + contact + ")"; + viewHolder.aw_player.setVisibility(View.GONE); + viewHolder.download_button.setVisibility(View.VISIBLE); + viewHolder.download_button.setText(add_contact); + viewHolder.download_button.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(body)); + activity.startActivity(intent); + } + }); + viewHolder.image.setVisibility(View.GONE); + viewHolder.messageBody.setVisibility(View.GONE); + + } + + private void displayTextMessage(final ViewHolder viewHolder, final Message message, boolean darkBackground) { + if (viewHolder.download_button != null) { + viewHolder.download_button.setVisibility(View.GONE); + } + viewHolder.image.setVisibility(View.GONE); + viewHolder.messageBody.setVisibility(View.VISIBLE); + viewHolder.messageBody.setIncludeFontPadding(true); + if (message.getBody() != null) { + final String nick = UIHelper.getMessageDisplayName(message); + SpannableStringBuilder body = message.getMergedBody(); + boolean hasMeCommand = message.hasMeCommand(); + if (hasMeCommand) { + body = body.replace(0, Message.ME_COMMAND.length(), nick + " "); + } + if (body.length() > Config.MAX_DISPLAY_MESSAGE_CHARS) { + body = new SpannableStringBuilder(body, 0, Config.MAX_DISPLAY_MESSAGE_CHARS); + body.append("\u2026"); + } + Message.MergeSeparator[] mergeSeparators = body.getSpans(0, body.length(), Message.MergeSeparator.class); + for (Message.MergeSeparator mergeSeparator : mergeSeparators) { + int start = body.getSpanStart(mergeSeparator); + int end = body.getSpanEnd(mergeSeparator); + body.setSpan(new RelativeSizeSpan(0.3f), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (message.getType() != Message.TYPE_PRIVATE) { + if (hasMeCommand) { + body.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 0, nick.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } else { + String privateMarker; + if (message.getStatus() <= Message.STATUS_RECEIVED) { + privateMarker = activity + .getString(R.string.private_message); + } else { + final String to; + if (message.getCounterpart() != null) { + to = message.getCounterpart().getResourcepart(); + } else { + to = ""; + } + privateMarker = activity.getString(R.string.private_message_to, to); + } + body.insert(0, privateMarker); + int privateMarkerIndex = privateMarker.length(); + body.insert(privateMarkerIndex, " "); + body.setSpan(new ForegroundColorSpan(getMessageTextColor(darkBackground, false)), 0, privateMarkerIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + body.setSpan(new StyleSpan(Typeface.BOLD), 0, privateMarkerIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + if (hasMeCommand) { + body.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), privateMarkerIndex + 1, privateMarkerIndex + 1 + nick.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + Linkify.addLinks(body, Linkify.WEB_URLS); + Linkify.addLinks(body, XMPP_PATTERN, "xmpp"); + Linkify.addLinks(body, GeoHelper.GEO_URI, "geo"); + viewHolder.messageBody.setAutoLinkMask(0); + viewHolder.messageBody.setText(body); + viewHolder.messageBody.setTextIsSelectable(true); + viewHolder.messageBody.setMovementMethod(ClickableMovementMethod.getInstance()); + listSelectionManager.onUpdate(viewHolder.messageBody, message); + + } else { + viewHolder.messageBody.setText(""); + viewHolder.messageBody.setTextIsSelectable(false); + } + viewHolder.messageBody.setTextColor(this.getMessageTextColor(darkBackground, true)); + viewHolder.messageBody.setLinkTextColor(this.getMessageTextColor(darkBackground, true)); + viewHolder.messageBody.setHighlightColor(activity.getResources().getColor(darkBackground ? R.color.grey800 : R.color.grey500)); + viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); + } + + private void displayDownloadableMessage(ViewHolder viewHolder, final Message message, String text) { + viewHolder.aw_player.setVisibility(View.GONE); + viewHolder.image.setVisibility(View.GONE); + viewHolder.messageBody.setVisibility(View.GONE); + viewHolder.download_button.setVisibility(View.VISIBLE); + viewHolder.download_button.setText(text); + viewHolder.download_button.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_download_grey600_48dp, 0, 0, 0); + viewHolder.download_button.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + activity.startDownloadable(message); + } + }); + } + + private void displayAudioMessage(ViewHolder viewHolder, final Message message, int position) { + if (audioPlayer == null) audioPlayer = new HashMap<>(); + viewHolder.image.setVisibility(View.GONE); + viewHolder.messageBody.setVisibility(View.GONE); + if (viewHolder.download_button != null) { + viewHolder.download_button.setVisibility(View.GONE); + } + viewHolder.aw_player.setVisibility(View.VISIBLE); + Uri audioFile = Uri.fromFile(activity.xmppConnectionService.getFileBackend().getFile(message)); + + audioWife = audioPlayer.get(position); + if (audioWife == null) { + audioWife = new AudioWife(); + audioWife.init(getContext(), audioFile); + audioPlayer.put(position, audioWife); + RelativeLayout vg = new RelativeLayout(activity); + LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + audioWife.useDefaultUi(vg, layoutInflater); + viewHolder.aw_player.addView(audioWife.getPlayerUi()); + } else { + audioWife.cleanPlayerUi(); + viewHolder.aw_player.addView(audioWife.getPlayerUi()); + } + } + + private void displayOpenableMessage(ViewHolder viewHolder, final Message message) { + viewHolder.aw_player.setVisibility(View.GONE); + viewHolder.image.setVisibility(View.GONE); + viewHolder.messageBody.setVisibility(View.GONE); + viewHolder.download_button.setVisibility(View.VISIBLE); + String mimeType = message.getMimeType(); + String fullName = ""; + if (mimeType != null) { + if (message.getMimeType().contains("pdf")) { + viewHolder.download_button.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_file_pdf_grey600_48dp, 0, 0, 0); + } else if (message.getMimeType().contains("vcard")) { + File file = new File(activity.xmppConnectionService.getFileBackend().getFile(message).toString()); + VCard vcard = null; + String name = null; + String version = null; + try { + vcard = Ezvcard.parse(file).first(); + } catch (IOException e) { + e.printStackTrace(); + } + if (vcard != null) { + version = vcard.getVersion().toString(); + Log.d(Config.LOGTAG, "VCard version: " + version); + name = vcard.getFormattedName().getValue(); + fullName = " (" + name + ")"; + } + viewHolder.download_button.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_account_card_details_grey600_48dp, 0, 0, 0); + } else if (message.getMimeType().contains("calendar")) { + viewHolder.download_button.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_calendar_grey600_48dp, 0, 0, 0); + } else { + viewHolder.download_button.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_file_grey600_48dp, 0, 0, 0); + } + } + viewHolder.download_button.setText(activity.getString(R.string.open_x_file, UIHelper.getFileDescriptionString(activity, message) + fullName)); + viewHolder.download_button.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + openDownloadable(message); + } + }); + } + + private void displayLocationMessage(ViewHolder viewHolder, final Message message) { + viewHolder.aw_player.setVisibility(View.GONE); + viewHolder.messageBody.setVisibility(View.GONE); + String url = GeoHelper.MapPreviewUri(message); + viewHolder.image.setVisibility(View.VISIBLE); + viewHolder.image.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + showLocation(message); + } + }); + Glide + .with(activity) + .load(Uri.parse(url)) + .asBitmap() + .diskCacheStrategy(DiskCacheStrategy.ALL) + .fitCenter() + .placeholder(R.drawable.ic_map_marker_grey600_48dp) + .error(R.drawable.ic_map_marker_grey600_48dp) + .into(viewHolder.image); + viewHolder.image.setMaxWidth(500); + viewHolder.image.setAdjustViewBounds(true); + viewHolder.download_button.setVisibility(View.GONE); + viewHolder.download_button.setText(R.string.show_location); + viewHolder.download_button.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_map_marker_grey600_48dp, 0, 0, 0); + viewHolder.download_button.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + showLocation(message); + } + }); + + } + + private void displayImageMessage(ViewHolder viewHolder, + final Message message) { + viewHolder.aw_player.setVisibility(View.GONE); + if (viewHolder.download_button != null) { + viewHolder.download_button.setVisibility(View.GONE); + } + viewHolder.messageBody.setVisibility(View.GONE); + viewHolder.image.setVisibility(View.VISIBLE); + FileParams params = message.getFileParams(); + double target = metrics.density * 200; + int scaledW; + int scaledH; + if (Math.max(params.height, params.width) * metrics.density <= target) { + scaledW = (int) (params.width * metrics.density); + scaledH = (int) (params.height * metrics.density); + } else if (Math.max(params.height, params.width) <= target) { + scaledW = params.width; + scaledH = params.height; + } else if (params.width <= params.height) { + scaledW = (int) (params.width / ((double) params.height / target)); + scaledH = (int) target; + } else { + scaledW = (int) target; + scaledH = (int) (params.height / ((double) params.width / target)); + } + LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(scaledW, scaledH); + layoutParams.setMargins(0, (int) (metrics.density * 4), 0, (int) (metrics.density * 4)); + viewHolder.image.setLayoutParams(layoutParams); + activity.loadBitmap(message, viewHolder.image); + viewHolder.image.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + openDownloadable(message); + } + }); + } + + private void loadMoreMessages(Conversation conversation) { + conversation.setLastClearHistory(0); + activity.xmppConnectionService.updateConversation(conversation); + conversation.setHasMessagesLeftOnServer(true); + conversation.setFirstMamReference(null); + long timestamp = conversation.getLastMessageTransmitted(); + if (timestamp == 0) { + timestamp = System.currentTimeMillis(); + } + activity.setMessagesLoaded(); + activity.xmppConnectionService.getMessageArchiveService().query(conversation, 0, timestamp); + Toast.makeText(activity, R.string.fetching_history_from_server, Toast.LENGTH_LONG).show(); + } + + @Override + public View getView(int position, View unused, ViewGroup parent) { + final Message message = getItem(position); + final boolean isInValidSession = message.isValidInSession(); + final Conversation conversation = message.getConversation(); + final Account account = conversation.getAccount(); + final int type = getItemViewType(position); + ViewHolder viewHolder; + View view; + viewHolder = new ViewHolder(); + switch (type) { + case SENT: + view = activity.getLayoutInflater().inflate( + R.layout.message_sent, parent, false); + viewHolder.message_box = (LinearLayout) view + .findViewById(R.id.message_box); + viewHolder.contact_picture = (ImageView) view + .findViewById(R.id.message_photo); + viewHolder.aw_player = (ViewGroup) view.findViewById(R.id.aw_player); + viewHolder.download_button = (Button) view + .findViewById(R.id.download_button); + viewHolder.indicator = (ImageView) view + .findViewById(R.id.security_indicator); + viewHolder.edit_indicator = (ImageView) view.findViewById(R.id.edit_indicator); + viewHolder.image = (ImageView) view + .findViewById(R.id.message_image); + viewHolder.messageBody = (CopyTextView) view + .findViewById(R.id.message_body); + viewHolder.time = (TextView) view + .findViewById(R.id.message_time); + viewHolder.indicatorReceived = (ImageView) view + .findViewById(R.id.indicator_received); + viewHolder.indicatorRead = (ImageView) view + .findViewById(R.id.indicator_read); + break; + case RECEIVED: + view = activity.getLayoutInflater().inflate( + R.layout.message_received, parent, false); + viewHolder.message_box = (LinearLayout) view + .findViewById(R.id.message_box); + viewHolder.contact_picture = (ImageView) view + .findViewById(R.id.message_photo); + viewHolder.aw_player = (ViewGroup) view.findViewById(R.id.aw_player); + viewHolder.download_button = (Button) view + .findViewById(R.id.download_button); + viewHolder.indicator = (ImageView) view + .findViewById(R.id.security_indicator); + viewHolder.edit_indicator = (ImageView) view.findViewById(R.id.edit_indicator); + viewHolder.image = (ImageView) view + .findViewById(R.id.message_image); + viewHolder.messageBody = (CopyTextView) view + .findViewById(R.id.message_body); + viewHolder.time = (TextView) view + .findViewById(R.id.message_time); + viewHolder.indicatorReceived = (ImageView) view + .findViewById(R.id.indicator_received); + viewHolder.encryption = (TextView) view.findViewById(R.id.message_encryption); + break; + case STATUS: + view = activity.getLayoutInflater().inflate(R.layout.message_status, parent, false); + viewHolder.contact_picture = (ImageView) view.findViewById(R.id.message_photo); + viewHolder.status_message = (TextView) view.findViewById(R.id.status_message); + viewHolder.load_more_messages = (Button) view.findViewById(R.id.load_more_messages); + break; + default: + view = new View(getContext()); + viewHolder = null; + break; + } + if (viewHolder.messageBody != null) { + listSelectionManager.onCreate(viewHolder.messageBody); + viewHolder.messageBody.setCopyHandler(this); + } + view.setTag(viewHolder); + if (viewHolder == null) { + return view; + } + + + boolean darkBackground = (type == SENT && (!isInValidSession || !mUseWhiteBackground)); + + if (type == STATUS) { + if ("LOAD_MORE".equals(message.getBody())) { + viewHolder.status_message.setVisibility(View.GONE); + viewHolder.contact_picture.setVisibility(View.GONE); + viewHolder.load_more_messages.setVisibility(View.VISIBLE); + viewHolder.load_more_messages.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + loadMoreMessages(message.getConversation()); + } + }); + } else { + viewHolder.status_message.setVisibility(View.VISIBLE); + viewHolder.contact_picture.setVisibility(View.VISIBLE); + viewHolder.load_more_messages.setVisibility(View.GONE); + if (conversation.getMode() == Conversation.MODE_SINGLE) { + viewHolder.contact_picture.setImageBitmap(activity + .avatarService().get(conversation.getContact(), + activity.getPixel(32))); + viewHolder.contact_picture.setAlpha(0.5f); + } + viewHolder.status_message.setText(message.getBody()); + } + return view; + } else { + loadAvatar(message, viewHolder.contact_picture); + } + + viewHolder.contact_picture + .setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + if (MessageAdapter.this.mOnContactPictureClickedListener != null) { + MessageAdapter.this.mOnContactPictureClickedListener + .onContactPictureClicked(message); + } + + } + }); + viewHolder.contact_picture + .setOnLongClickListener(new OnLongClickListener() { + + @Override + public boolean onLongClick(View v) { + if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) { + MessageAdapter.this.mOnContactPictureLongClickedListener + .onContactPictureLongClicked(message); + return true; + } else { + return false; + } + } + }); + + final Transferable transferable = message.getTransferable(); + String mimeType = message.getMimeType(); + if (transferable != null && transferable.getStatus() != Transferable.STATUS_UPLOADING) { + if (transferable.getStatus() == Transferable.STATUS_OFFER) { + displayDownloadableMessage(viewHolder, message, activity.getString(R.string.download_x_file, UIHelper.getFileDescriptionString(activity, message))); + } else if (transferable.getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE) { + displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message))); + } else { + displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first, darkBackground); + } + } else if (message.getType() == Message.TYPE_IMAGE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) { + displayImageMessage(viewHolder, message); + } else if (message.getType() == Message.TYPE_FILE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) { + if (message.getFileParams().width > 0) { + displayImageMessage(viewHolder, message); + } else { + if (mimeType != null) { + if (message.getMimeType().startsWith("audio/")) { + displayAudioMessage(viewHolder, message, position); + } else displayOpenableMessage(viewHolder, message); + } else displayOpenableMessage(viewHolder, message); + } + } else if (message.getEncryption() == Message.ENCRYPTION_PGP) { + if (account.isPgpDecryptionServiceConnected()) { + if (!account.hasPendingPgpIntent(conversation)) { + displayInfoMessage(viewHolder, activity.getString(R.string.message_decrypting), darkBackground); + } else { + displayInfoMessage(viewHolder, activity.getString(R.string.pgp_message), darkBackground); + } + } else { + displayInfoMessage(viewHolder, activity.getString(R.string.install_openkeychain), darkBackground); + if (viewHolder != null) { + viewHolder.message_box + .setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + activity.showInstallPgpDialog(); + } + }); + } + } + } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { + displayDecryptionFailed(viewHolder, darkBackground); + } else { + if (GeoHelper.isGeoUri(message.getBody())) { + displayLocationMessage(viewHolder, message); + } else if (message.bodyIsHeart()) { + displayHeartMessage(viewHolder, message.getBody().trim()); + } else if (message.bodyIsXmpp()) { + displayXmppMessage(viewHolder, message.getBody().trim()); + } else if (message.treatAsDownloadable() == Message.Decision.MUST || + message.treatAsDownloadable() == Message.Decision.SHOULD) { + try { + URL url = new URL(message.getBody()); + displayDownloadableMessage(viewHolder, + message, + activity.getString(R.string.check_x_filesize_on_host, + UIHelper.getFileDescriptionString(activity, message), + url.getHost())); + } catch (Exception e) { + displayDownloadableMessage(viewHolder, + message, + activity.getString(R.string.check_x_filesize, + UIHelper.getFileDescriptionString(activity, message))); + } + } else { + displayTextMessage(viewHolder, message, darkBackground); + } + } + + if (type == RECEIVED) { + if (isInValidSession) { + viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received); + viewHolder.encryption.setVisibility(View.GONE); + } else { + viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received_warning); + viewHolder.encryption.setVisibility(View.VISIBLE); + viewHolder.encryption.setText(CryptoHelper.encryptionTypeToText(message.getEncryption())); + } + } + + if (type == SENT) { + if (mUseWhiteBackground) { + viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_sent_white); + } else { + viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_sent); + } + } + + displayStatus(viewHolder, message, type, darkBackground); + + return view; + } + + @Override + public void notifyDataSetChanged() { + listSelectionManager.onBeforeNotifyDataSetChanged(); + super.notifyDataSetChanged(); + listSelectionManager.onAfterNotifyDataSetChanged(); + } + + @Override + public String transformTextForCopy(CharSequence text, int start, int end) { + return text.toString().substring(start, end); + } + + public void openDownloadable(Message message) { + DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message); + if (!file.exists()) { + Toast.makeText(activity, R.string.file_deleted, Toast.LENGTH_SHORT).show(); + return; + } + String mime = file.getMimeType(); + if (mime.startsWith("image/")) { + Intent intent = new Intent(getContext(), ShowFullscreenMessageActivity.class); + intent.putExtra("image", Uri.fromFile(file)); + try { + activity.startActivity(intent); + return; + } catch (ActivityNotFoundException e) { + //ignored + } + } else if (mime.startsWith("video/")) { + Intent intent = new Intent(getContext(), ShowFullscreenMessageActivity.class); + intent.putExtra("video", Uri.fromFile(file)); + try { + activity.startActivity(intent); + return; + } catch (ActivityNotFoundException e) { + //ignored + } + } + Intent openIntent = new Intent(Intent.ACTION_VIEW); + if (mime == null) { + mime = "*/*"; + } + Uri uri; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + try { + uri = FileProvider.getUriForFile(activity, FileBackend.CONVERSATIONS_FILE_PROVIDER, file); + } catch (IllegalArgumentException e) { + Toast.makeText(activity, activity.getString(R.string.no_permission_to_access_x, file.getAbsolutePath()), Toast.LENGTH_SHORT).show(); + return; + } + openIntent.setDataAndType(uri, mime); + openIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + } else { + uri = Uri.fromFile(file); + } + openIntent.setDataAndType(uri, mime); + PackageManager manager = activity.getPackageManager(); + List info = manager.queryIntentActivities(openIntent, 0); + if (info.size() == 0) { + openIntent.setDataAndType(Uri.fromFile(file), "*/*"); + } + try { + getContext().startActivity(openIntent); + } catch (ActivityNotFoundException e) { + Toast.makeText(activity, R.string.no_application_found_to_open_file, Toast.LENGTH_SHORT).show(); + } + } + + public void showLocation(Message message) { + for (Intent intent : GeoHelper.createGeoIntentsFromMessage(message)) { + if (intent.resolveActivity(getContext().getPackageManager()) != null) { + getContext().startActivity(intent); + return; + } + } + Toast.makeText(activity, R.string.no_application_found_to_display_location, Toast.LENGTH_SHORT).show(); + } + + public void updatePreferences() { + this.mIndicateReceived = activity.indicateReceived(); + this.mUseWhiteBackground = activity.useWhiteBackground(); + } + + public TextView getMessageBody(View view) { + final Object tag = view.getTag(); + if (tag instanceof ViewHolder) { + final ViewHolder viewHolder = (ViewHolder) tag; + return viewHolder.messageBody; + } + return null; + } + + public interface OnContactPictureClicked { + void onContactPictureClicked(Message message); + } + + public interface OnContactPictureLongClicked { + void onContactPictureLongClicked(Message message); + } + + private static class ViewHolder { + + protected LinearLayout message_box; + protected Button download_button; + protected ViewGroup aw_player; + protected ImageView image; + protected ImageView indicator; + protected ImageView indicatorReceived; + protected ImageView indicatorRead; + protected TextView time; + protected CopyTextView messageBody; + protected ImageView contact_picture; + protected TextView status_message; + protected TextView encryption; + public Button load_more_messages; + public ImageView edit_indicator; + } + + class BitmapWorkerTask extends AsyncTask { + private final WeakReference imageViewReference; + private Message message = null; + + public BitmapWorkerTask(ImageView imageView) { + imageViewReference = new WeakReference<>(imageView); + } + + @Override + protected Bitmap doInBackground(Message... params) { + return activity.avatarService().get(params[0], activity.getPixel(48), isCancelled()); + } + + @Override + protected void onPostExecute(Bitmap bitmap) { + if (bitmap != null && !isCancelled()) { + final ImageView imageView = imageViewReference.get(); + if (imageView != null) { + imageView.setImageBitmap(bitmap); + imageView.setBackgroundColor(0x00000000); + } + } + } + } + + public void loadAvatar(Message message, ImageView imageView) { + if (cancelPotentialWork(message, imageView)) { + final Bitmap bm = activity.avatarService().get(message, activity.getPixel(48), true); + if (bm != null) { + cancelPotentialWork(message, imageView); + imageView.setImageBitmap(bm); + imageView.setBackgroundColor(0x00000000); + } else { + imageView.setBackgroundColor(UIHelper.getColorForName(UIHelper.getMessageDisplayName(message))); + imageView.setImageDrawable(null); + final BitmapWorkerTask task = new BitmapWorkerTask(imageView); + final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task); + imageView.setImageDrawable(asyncDrawable); + try { + task.execute(message); + } catch (final RejectedExecutionException ignored) { + } + } + } + } + + public static boolean cancelPotentialWork(Message message, ImageView imageView) { + final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); + + if (bitmapWorkerTask != null) { + final Message oldMessage = bitmapWorkerTask.message; + if (oldMessage == null || message != oldMessage) { + bitmapWorkerTask.cancel(true); + } else { + return false; + } + } + return true; + } + + private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { + if (imageView != null) { + final Drawable drawable = imageView.getDrawable(); + if (drawable instanceof AsyncDrawable) { + final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; + return asyncDrawable.getBitmapWorkerTask(); + } + } + return null; + } + + static class AsyncDrawable extends BitmapDrawable { + private final WeakReference bitmapWorkerTaskReference; + + public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) { + super(res, bitmap); + bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask); + } + + public BitmapWorkerTask getBitmapWorkerTask() { + return bitmapWorkerTaskReference.get(); + } + } } diff --git a/src/main/java/de/pixart/messenger/ui/forms/FormBooleanFieldWrapper.java b/src/main/java/de/pixart/messenger/ui/forms/FormBooleanFieldWrapper.java index 2593ac204..51ac79c66 100644 --- a/src/main/java/de/pixart/messenger/ui/forms/FormBooleanFieldWrapper.java +++ b/src/main/java/de/pixart/messenger/ui/forms/FormBooleanFieldWrapper.java @@ -12,69 +12,69 @@ import de.pixart.messenger.xmpp.forms.Field; public class FormBooleanFieldWrapper extends FormFieldWrapper { - protected CheckBox checkBox; + protected CheckBox checkBox; - protected FormBooleanFieldWrapper(Context context, Field field) { - super(context, field); - checkBox = (CheckBox) view.findViewById(R.id.field); - checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - checkBox.setError(null); - invokeOnFormFieldValuesEdited(); - } - }); - } + protected FormBooleanFieldWrapper(Context context, Field field) { + super(context, field); + checkBox = (CheckBox) view.findViewById(R.id.field); + checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + checkBox.setError(null); + invokeOnFormFieldValuesEdited(); + } + }); + } - @Override - protected void setLabel(String label, boolean required) { - CheckBox checkBox = (CheckBox) view.findViewById(R.id.field); - checkBox.setText(createSpannableLabelString(label, required)); - } + @Override + protected void setLabel(String label, boolean required) { + CheckBox checkBox = (CheckBox) view.findViewById(R.id.field); + checkBox.setText(createSpannableLabelString(label, required)); + } - @Override - public List getValues() { - List values = new ArrayList<>(); - values.add(Boolean.toString(checkBox.isChecked())); - return values; - } + @Override + public List getValues() { + List values = new ArrayList<>(); + values.add(Boolean.toString(checkBox.isChecked())); + return values; + } - @Override - protected void setValues(List values) { - if (values.size() == 0) { - checkBox.setChecked(false); - } else { - checkBox.setChecked(Boolean.parseBoolean(values.get(0))); - } - } + @Override + protected void setValues(List values) { + if (values.size() == 0) { + checkBox.setChecked(false); + } else { + checkBox.setChecked(Boolean.parseBoolean(values.get(0))); + } + } - @Override - public boolean validates() { - if (checkBox.isChecked() || !field.isRequired()) { - return true; - } else { - checkBox.setError(context.getString(R.string.this_field_is_required)); - checkBox.requestFocus(); - return false; - } - } + @Override + public boolean validates() { + if (checkBox.isChecked() || !field.isRequired()) { + return true; + } else { + checkBox.setError(context.getString(R.string.this_field_is_required)); + checkBox.requestFocus(); + return false; + } + } - @Override - public boolean edited() { - if (field.getValues().size() == 0) { - return checkBox.isChecked(); - } else { - return super.edited(); - } - } + @Override + public boolean edited() { + if (field.getValues().size() == 0) { + return checkBox.isChecked(); + } else { + return super.edited(); + } + } - @Override - protected int getLayoutResource() { - return R.layout.form_boolean; - } + @Override + protected int getLayoutResource() { + return R.layout.form_boolean; + } - @Override - void setReadOnly(boolean readOnly) { - checkBox.setEnabled(!readOnly); - } + @Override + void setReadOnly(boolean readOnly) { + checkBox.setEnabled(!readOnly); + } } diff --git a/src/main/java/de/pixart/messenger/ui/forms/FormFieldFactory.java b/src/main/java/de/pixart/messenger/ui/forms/FormFieldFactory.java index c1318b9f1..5a30e35a5 100644 --- a/src/main/java/de/pixart/messenger/ui/forms/FormFieldFactory.java +++ b/src/main/java/de/pixart/messenger/ui/forms/FormFieldFactory.java @@ -7,24 +7,23 @@ import java.util.Hashtable; import de.pixart.messenger.xmpp.forms.Field; - public class FormFieldFactory { - private static final Hashtable typeTable = new Hashtable<>(); - - static { - typeTable.put("text-single", FormTextFieldWrapper.class); - typeTable.put("text-multi", FormTextFieldWrapper.class); - typeTable.put("text-private", FormTextFieldWrapper.class); - typeTable.put("jid-single", FormJidSingleFieldWrapper.class); - typeTable.put("boolean", FormBooleanFieldWrapper.class); - } - - protected static FormFieldWrapper createFromField(Context context, Field field) { - Class clazz = typeTable.get(field.getType()); - if (clazz == null) { - clazz = FormTextFieldWrapper.class; - } - return FormFieldWrapper.createFromField(clazz, context, field); - } + private static final Hashtable typeTable = new Hashtable<>(); + + static { + typeTable.put("text-single", FormTextFieldWrapper.class); + typeTable.put("text-multi", FormTextFieldWrapper.class); + typeTable.put("text-private", FormTextFieldWrapper.class); + typeTable.put("jid-single", FormJidSingleFieldWrapper.class); + typeTable.put("boolean", FormBooleanFieldWrapper.class); + } + + protected static FormFieldWrapper createFromField(Context context, Field field) { + Class clazz = typeTable.get(field.getType()); + if (clazz == null) { + clazz = FormTextFieldWrapper.class; + } + return FormFieldWrapper.createFromField(clazz, context, field); + } } diff --git a/src/main/java/de/pixart/messenger/ui/forms/FormFieldWrapper.java b/src/main/java/de/pixart/messenger/ui/forms/FormFieldWrapper.java index 5182f3e7a..01b9ea0f9 100644 --- a/src/main/java/de/pixart/messenger/ui/forms/FormFieldWrapper.java +++ b/src/main/java/de/pixart/messenger/ui/forms/FormFieldWrapper.java @@ -14,80 +14,80 @@ import de.pixart.messenger.xmpp.forms.Field; public abstract class FormFieldWrapper { - protected final Context context; - protected final Field field; - protected final View view; - protected OnFormFieldValuesEdited onFormFieldValuesEditedListener; - - protected FormFieldWrapper(Context context, Field field) { - this.context = context; - this.field = field; - LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - this.view = inflater.inflate(getLayoutResource(), null); - String label = field.getLabel(); - if (label == null) { - label = field.getFieldName(); - } - setLabel(label, field.isRequired()); - } - - public final void submit() { - this.field.setValues(getValues()); - } - - public final View getView() { - return view; - } - - protected abstract void setLabel(String label, boolean required); - - abstract List getValues(); - - protected abstract void setValues(List values); - - abstract boolean validates(); - - abstract protected int getLayoutResource(); - - abstract void setReadOnly(boolean readOnly); - - protected SpannableString createSpannableLabelString(String label, boolean required) { - SpannableString spannableString = new SpannableString(label + (required ? " *" : "")); - if (required) { - int start = label.length(); - int end = label.length() + 2; - spannableString.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), start, end, 0); - spannableString.setSpan(new ForegroundColorSpan(context.getResources().getColor(R.color.accent)), start, end, 0); - } - return spannableString; - } - - protected void invokeOnFormFieldValuesEdited() { - if (this.onFormFieldValuesEditedListener != null) { - this.onFormFieldValuesEditedListener.onFormFieldValuesEdited(); - } - } - - public boolean edited() { - return !field.getValues().equals(getValues()); - } - - public void setOnFormFieldValuesEditedListener(OnFormFieldValuesEdited listener) { - this.onFormFieldValuesEditedListener = listener; - } - - protected static FormFieldWrapper createFromField(Class c, Context context, Field field) { - try { - F fieldWrapper = c.getDeclaredConstructor(Context.class, Field.class).newInstance(context,field); - fieldWrapper.setValues(field.getValues()); - return fieldWrapper; - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - public interface OnFormFieldValuesEdited { - void onFormFieldValuesEdited(); - } + protected final Context context; + protected final Field field; + protected final View view; + protected OnFormFieldValuesEdited onFormFieldValuesEditedListener; + + protected FormFieldWrapper(Context context, Field field) { + this.context = context; + this.field = field; + LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + this.view = inflater.inflate(getLayoutResource(), null); + String label = field.getLabel(); + if (label == null) { + label = field.getFieldName(); + } + setLabel(label, field.isRequired()); + } + + public final void submit() { + this.field.setValues(getValues()); + } + + public final View getView() { + return view; + } + + protected abstract void setLabel(String label, boolean required); + + abstract List getValues(); + + protected abstract void setValues(List values); + + abstract boolean validates(); + + abstract protected int getLayoutResource(); + + abstract void setReadOnly(boolean readOnly); + + protected SpannableString createSpannableLabelString(String label, boolean required) { + SpannableString spannableString = new SpannableString(label + (required ? " *" : "")); + if (required) { + int start = label.length(); + int end = label.length() + 2; + spannableString.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), start, end, 0); + spannableString.setSpan(new ForegroundColorSpan(context.getResources().getColor(R.color.accent)), start, end, 0); + } + return spannableString; + } + + protected void invokeOnFormFieldValuesEdited() { + if (this.onFormFieldValuesEditedListener != null) { + this.onFormFieldValuesEditedListener.onFormFieldValuesEdited(); + } + } + + public boolean edited() { + return !field.getValues().equals(getValues()); + } + + public void setOnFormFieldValuesEditedListener(OnFormFieldValuesEdited listener) { + this.onFormFieldValuesEditedListener = listener; + } + + protected static FormFieldWrapper createFromField(Class c, Context context, Field field) { + try { + F fieldWrapper = c.getDeclaredConstructor(Context.class, Field.class).newInstance(context, field); + fieldWrapper.setValues(field.getValues()); + return fieldWrapper; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + public interface OnFormFieldValuesEdited { + void onFormFieldValuesEdited(); + } } diff --git a/src/main/java/de/pixart/messenger/ui/forms/FormJidSingleFieldWrapper.java b/src/main/java/de/pixart/messenger/ui/forms/FormJidSingleFieldWrapper.java index 8d37f259f..b6aeaf344 100644 --- a/src/main/java/de/pixart/messenger/ui/forms/FormJidSingleFieldWrapper.java +++ b/src/main/java/de/pixart/messenger/ui/forms/FormJidSingleFieldWrapper.java @@ -12,33 +12,33 @@ import de.pixart.messenger.xmpp.jid.Jid; public class FormJidSingleFieldWrapper extends FormTextFieldWrapper { - protected FormJidSingleFieldWrapper(Context context, Field field) { - super(context, field); - editText.setInputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS); - editText.setHint(R.string.account_settings_example_jabber_id); - } + protected FormJidSingleFieldWrapper(Context context, Field field) { + super(context, field); + editText.setInputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS); + editText.setHint(R.string.account_settings_example_jabber_id); + } - @Override - public boolean validates() { - String value = getValue(); - if (!value.isEmpty()) { - try { - Jid.fromString(value); - } catch (InvalidJidException e) { - editText.setError(context.getString(R.string.invalid_jid)); - editText.requestFocus(); - return false; - } - } - return super.validates(); - } + @Override + public boolean validates() { + String value = getValue(); + if (!value.isEmpty()) { + try { + Jid.fromString(value); + } catch (InvalidJidException e) { + editText.setError(context.getString(R.string.invalid_jid)); + editText.requestFocus(); + return false; + } + } + return super.validates(); + } - @Override - protected void setValues(List values) { - StringBuilder builder = new StringBuilder(""); - for(String value : values) { - builder.append(value); - } - editText.setText(builder.toString()); - } + @Override + protected void setValues(List values) { + StringBuilder builder = new StringBuilder(""); + for (String value : values) { + builder.append(value); + } + editText.setText(builder.toString()); + } } diff --git a/src/main/java/de/pixart/messenger/ui/forms/FormTextFieldWrapper.java b/src/main/java/de/pixart/messenger/ui/forms/FormTextFieldWrapper.java index cccfd1608..5635bc7b4 100644 --- a/src/main/java/de/pixart/messenger/ui/forms/FormTextFieldWrapper.java +++ b/src/main/java/de/pixart/messenger/ui/forms/FormTextFieldWrapper.java @@ -15,83 +15,83 @@ import de.pixart.messenger.xmpp.forms.Field; public class FormTextFieldWrapper extends FormFieldWrapper { - protected EditText editText; + protected EditText editText; - protected FormTextFieldWrapper(Context context, Field field) { - super(context, field); - editText = (EditText) view.findViewById(R.id.field); - editText.setSingleLine(!"text-multi".equals(field.getType())); - if ("text-private".equals(field.getType())) { - editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); - } - editText.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } + protected FormTextFieldWrapper(Context context, Field field) { + super(context, field); + editText = (EditText) view.findViewById(R.id.field); + editText.setSingleLine(!"text-multi".equals(field.getType())); + if ("text-private".equals(field.getType())) { + editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + } + editText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - editText.setError(null); - invokeOnFormFieldValuesEdited(); - } + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + editText.setError(null); + invokeOnFormFieldValuesEdited(); + } - @Override - public void afterTextChanged(Editable s) { - } - }); - } + @Override + public void afterTextChanged(Editable s) { + } + }); + } - @Override - protected void setLabel(String label, boolean required) { - TextView textView = (TextView) view.findViewById(R.id.label); - textView.setText(createSpannableLabelString(label, required)); - } + @Override + protected void setLabel(String label, boolean required) { + TextView textView = (TextView) view.findViewById(R.id.label); + textView.setText(createSpannableLabelString(label, required)); + } - protected String getValue() { - return editText.getText().toString(); - } + protected String getValue() { + return editText.getText().toString(); + } - @Override - public List getValues() { - List values = new ArrayList<>(); - for (String line : getValue().split("\\n")) { - if (line.length() > 0) { - values.add(line); - } - } - return values; - } + @Override + public List getValues() { + List values = new ArrayList<>(); + for (String line : getValue().split("\\n")) { + if (line.length() > 0) { + values.add(line); + } + } + return values; + } - @Override - protected void setValues(List values) { - StringBuilder builder = new StringBuilder(""); - for(int i = 0; i < values.size(); ++i) { - builder.append(values.get(i)); - if (i < values.size() - 1 && "text-multi".equals(field.getType())) { - builder.append("\n"); - } - } - editText.setText(builder.toString()); - } + @Override + protected void setValues(List values) { + StringBuilder builder = new StringBuilder(""); + for (int i = 0; i < values.size(); ++i) { + builder.append(values.get(i)); + if (i < values.size() - 1 && "text-multi".equals(field.getType())) { + builder.append("\n"); + } + } + editText.setText(builder.toString()); + } - @Override - public boolean validates() { - if (getValue().trim().length() > 0 || !field.isRequired()) { - return true; - } else { - editText.setError(context.getString(R.string.this_field_is_required)); - editText.requestFocus(); - return false; - } - } + @Override + public boolean validates() { + if (getValue().trim().length() > 0 || !field.isRequired()) { + return true; + } else { + editText.setError(context.getString(R.string.this_field_is_required)); + editText.requestFocus(); + return false; + } + } - @Override - protected int getLayoutResource() { - return R.layout.form_text; - } + @Override + protected int getLayoutResource() { + return R.layout.form_text; + } - @Override - void setReadOnly(boolean readOnly) { - editText.setEnabled(!readOnly); - } + @Override + void setReadOnly(boolean readOnly) { + editText.setEnabled(!readOnly); + } } diff --git a/src/main/java/de/pixart/messenger/ui/forms/FormWrapper.java b/src/main/java/de/pixart/messenger/ui/forms/FormWrapper.java index 05fa922b5..f22bef721 100644 --- a/src/main/java/de/pixart/messenger/ui/forms/FormWrapper.java +++ b/src/main/java/de/pixart/messenger/ui/forms/FormWrapper.java @@ -11,62 +11,62 @@ import de.pixart.messenger.xmpp.forms.Field; public class FormWrapper { - private final LinearLayout layout; + private final LinearLayout layout; - private final Data form; + private final Data form; - private final List fieldWrappers = new ArrayList<>(); + private final List fieldWrappers = new ArrayList<>(); - private FormWrapper(Context context, LinearLayout linearLayout, Data form) { - this.form = form; - this.layout = linearLayout; - this.layout.removeAllViews(); - for(Field field : form.getFields()) { - FormFieldWrapper fieldWrapper = FormFieldFactory.createFromField(context,field); - if (fieldWrapper != null) { - layout.addView(fieldWrapper.getView()); - fieldWrappers.add(fieldWrapper); - } - } - } + private FormWrapper(Context context, LinearLayout linearLayout, Data form) { + this.form = form; + this.layout = linearLayout; + this.layout.removeAllViews(); + for (Field field : form.getFields()) { + FormFieldWrapper fieldWrapper = FormFieldFactory.createFromField(context, field); + if (fieldWrapper != null) { + layout.addView(fieldWrapper.getView()); + fieldWrappers.add(fieldWrapper); + } + } + } - public Data submit() { - for(FormFieldWrapper fieldWrapper : fieldWrappers) { - fieldWrapper.submit(); - } - this.form.submit(); - return this.form; - } + public Data submit() { + for (FormFieldWrapper fieldWrapper : fieldWrappers) { + fieldWrapper.submit(); + } + this.form.submit(); + return this.form; + } - public boolean validates() { - boolean validates = true; - for(FormFieldWrapper fieldWrapper : fieldWrappers) { - validates &= fieldWrapper.validates(); - } - return validates; - } + public boolean validates() { + boolean validates = true; + for (FormFieldWrapper fieldWrapper : fieldWrappers) { + validates &= fieldWrapper.validates(); + } + return validates; + } - public void setOnFormFieldValuesEditedListener(FormFieldWrapper.OnFormFieldValuesEdited listener) { - for(FormFieldWrapper fieldWrapper : fieldWrappers) { - fieldWrapper.setOnFormFieldValuesEditedListener(listener); - } - } + public void setOnFormFieldValuesEditedListener(FormFieldWrapper.OnFormFieldValuesEdited listener) { + for (FormFieldWrapper fieldWrapper : fieldWrappers) { + fieldWrapper.setOnFormFieldValuesEditedListener(listener); + } + } - public void setReadOnly(boolean b) { - for(FormFieldWrapper fieldWrapper : fieldWrappers) { - fieldWrapper.setReadOnly(b); - } - } + public void setReadOnly(boolean b) { + for (FormFieldWrapper fieldWrapper : fieldWrappers) { + fieldWrapper.setReadOnly(b); + } + } - public boolean edited() { - boolean edited = false; - for(FormFieldWrapper fieldWrapper : fieldWrappers) { - edited |= fieldWrapper.edited(); - } - return edited; - } + public boolean edited() { + boolean edited = false; + for (FormFieldWrapper fieldWrapper : fieldWrappers) { + edited |= fieldWrapper.edited(); + } + return edited; + } - public static FormWrapper createInLayout(Context context, LinearLayout layout, Data form) { - return new FormWrapper(context, layout, form); - } + public static FormWrapper createInLayout(Context context, LinearLayout layout, Data form) { + return new FormWrapper(context, layout, form); + } } diff --git a/src/main/java/de/pixart/messenger/ui/widget/ClickableMovementMethod.java b/src/main/java/de/pixart/messenger/ui/widget/ClickableMovementMethod.java index dbf88f1d7..c399a7864 100644 --- a/src/main/java/de/pixart/messenger/ui/widget/ClickableMovementMethod.java +++ b/src/main/java/de/pixart/messenger/ui/widget/ClickableMovementMethod.java @@ -9,34 +9,34 @@ import android.widget.TextView; public class ClickableMovementMethod extends ArrowKeyMovementMethod { - @Override - public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { - // Just copied from android.text.method.LinkMovementMethod - if (event.getAction() == MotionEvent.ACTION_UP) { - int x = (int) event.getX(); - int y = (int) event.getY(); - x -= widget.getTotalPaddingLeft(); - y -= widget.getTotalPaddingTop(); - x += widget.getScrollX(); - y += widget.getScrollY(); - Layout layout = widget.getLayout(); - int line = layout.getLineForVertical(y); - int off = layout.getOffsetForHorizontal(line, x); - ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class); - if (link.length != 0) { - link[0].onClick(widget); - return true; - } - } - return super.onTouchEvent(widget, buffer, event); - } + @Override + public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { + // Just copied from android.text.method.LinkMovementMethod + if (event.getAction() == MotionEvent.ACTION_UP) { + int x = (int) event.getX(); + int y = (int) event.getY(); + x -= widget.getTotalPaddingLeft(); + y -= widget.getTotalPaddingTop(); + x += widget.getScrollX(); + y += widget.getScrollY(); + Layout layout = widget.getLayout(); + int line = layout.getLineForVertical(y); + int off = layout.getOffsetForHorizontal(line, x); + ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class); + if (link.length != 0) { + link[0].onClick(widget); + return true; + } + } + return super.onTouchEvent(widget, buffer, event); + } - public static ClickableMovementMethod getInstance() { - if (sInstance == null) { - sInstance = new ClickableMovementMethod(); - } - return sInstance; - } + public static ClickableMovementMethod getInstance() { + if (sInstance == null) { + sInstance = new ClickableMovementMethod(); + } + return sInstance; + } - private static ClickableMovementMethod sInstance; + private static ClickableMovementMethod sInstance; } \ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/widget/Switch.java b/src/main/java/de/pixart/messenger/ui/widget/Switch.java index 2bd4eda3d..40d8f4c99 100644 --- a/src/main/java/de/pixart/messenger/ui/widget/Switch.java +++ b/src/main/java/de/pixart/messenger/ui/widget/Switch.java @@ -9,60 +9,60 @@ import com.kyleduo.switchbutton.SwitchButton; public class Switch extends SwitchButton { - private int mTouchSlop; - private int mClickTimeout; - private float mStartX; - private float mStartY; - private OnClickListener mOnClickListener; + private int mTouchSlop; + private int mClickTimeout; + private float mStartX; + private float mStartY; + private OnClickListener mOnClickListener; - public Switch(Context context) { - super(context); - mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); - mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout(); - } + public Switch(Context context) { + super(context); + mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout(); + } - public Switch(Context context, AttributeSet attrs) { - super(context, attrs); - mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); - mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout(); - } + public Switch(Context context, AttributeSet attrs) { + super(context, attrs); + mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout(); + } - public Switch(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); - mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout(); - } + public Switch(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout(); + } - @Override - public void setOnClickListener(OnClickListener onClickListener) { - this.mOnClickListener = onClickListener; - } + @Override + public void setOnClickListener(OnClickListener onClickListener) { + this.mOnClickListener = onClickListener; + } - @Override - public boolean onTouchEvent(MotionEvent event) { - if (!isEnabled()) { - float deltaX = event.getX() - mStartX; - float deltaY = event.getY() - mStartY; - int action = event.getAction(); - switch (action) { - case MotionEvent.ACTION_DOWN: - mStartX = event.getX(); - mStartY = event.getY(); - break; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - float time = event.getEventTime() - event.getDownTime(); - if (deltaX < mTouchSlop && deltaY < mTouchSlop && time < mClickTimeout) { - if (mOnClickListener != null) { - this.mOnClickListener.onClick(this); - } - } - break; - default: - break; - } - return true; - } - return super.onTouchEvent(event); - } + @Override + public boolean onTouchEvent(MotionEvent event) { + if (!isEnabled()) { + float deltaX = event.getX() - mStartX; + float deltaY = event.getY() - mStartY; + int action = event.getAction(); + switch (action) { + case MotionEvent.ACTION_DOWN: + mStartX = event.getX(); + mStartY = event.getY(); + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + float time = event.getEventTime() - event.getDownTime(); + if (deltaX < mTouchSlop && deltaY < mTouchSlop && time < mClickTimeout) { + if (mOnClickListener != null) { + this.mOnClickListener.onClick(this); + } + } + break; + default: + break; + } + return true; + } + return super.onTouchEvent(event); + } } diff --git a/src/main/java/de/pixart/messenger/utils/ConversationsFileObserver.java b/src/main/java/de/pixart/messenger/utils/ConversationsFileObserver.java index fbd4c9708..dd2d673b4 100644 --- a/src/main/java/de/pixart/messenger/utils/ConversationsFileObserver.java +++ b/src/main/java/de/pixart/messenger/utils/ConversationsFileObserver.java @@ -35,7 +35,7 @@ public abstract class ConversationsFileObserver { if (files == null) { continue; } - for(File file : files) { + for (File file : files) { if (file.isDirectory() && !file.getName().equals(".") && !file.getName().equals("..")) { final String currentPath = file.getAbsolutePath(); if (depth(file) <= 8 && !stack.contains(currentPath) && !observing(currentPath)) { @@ -44,22 +44,22 @@ public abstract class ConversationsFileObserver { } } } - for(FileObserver observer : mObservers) { + for (FileObserver observer : mObservers) { observer.startWatching(); } } private static int depth(File file) { int depth = 0; - while((file = file.getParentFile()) != null) { + while ((file = file.getParentFile()) != null) { depth++; } return depth; } private boolean observing(String path) { - for(SingleFileObserver observer : mObservers) { - if(path.equals(observer.path)) { + for (SingleFileObserver observer : mObservers) { + if (path.equals(observer.path)) { return true; } } @@ -67,7 +67,7 @@ public abstract class ConversationsFileObserver { } public synchronized void stopWatching() { - for(FileObserver observer : mObservers) { + for (FileObserver observer : mObservers) { observer.stopWatching(); } mObservers.clear(); @@ -85,7 +85,7 @@ public abstract class ConversationsFileObserver { @Override public void onEvent(int event, String filename) { - ConversationsFileObserver.this.onEvent(event, path+'/'+filename); + ConversationsFileObserver.this.onEvent(event, path + '/' + filename); } } diff --git a/src/main/java/de/pixart/messenger/utils/CryptoHelper.java b/src/main/java/de/pixart/messenger/utils/CryptoHelper.java index 510f60352..ec4f750fd 100644 --- a/src/main/java/de/pixart/messenger/utils/CryptoHelper.java +++ b/src/main/java/de/pixart/messenger/utils/CryptoHelper.java @@ -31,179 +31,179 @@ import de.pixart.messenger.xmpp.jid.InvalidJidException; import de.pixart.messenger.xmpp.jid.Jid; public final class CryptoHelper { - public static final String FILETRANSFER = "?FILETRANSFERv1:"; - private final static char[] hexArray = "0123456789abcdef".toCharArray(); - - public static final Pattern UUID_PATTERN = Pattern.compile("[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"); - final public static byte[] ONE = new byte[] { 0, 0, 0, 1 }; - - public static String bytesToHex(byte[] bytes) { - char[] hexChars = new char[bytes.length * 2]; - for (int j = 0; j < bytes.length; j++) { - int v = bytes[j] & 0xFF; - hexChars[j * 2] = hexArray[v >>> 4]; - hexChars[j * 2 + 1] = hexArray[v & 0x0F]; - } - return new String(hexChars); - } - - public static byte[] hexToBytes(String hexString) { - int len = hexString.length(); - byte[] array = new byte[len / 2]; - for (int i = 0; i < len; i += 2) { - array[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character - .digit(hexString.charAt(i + 1), 16)); - } - return array; - } - - public static String hexToString(final String hexString) { - return new String(hexToBytes(hexString)); - } - - public static byte[] concatenateByteArrays(byte[] a, byte[] b) { - byte[] result = new byte[a.length + b.length]; - System.arraycopy(a, 0, result, 0, a.length); - System.arraycopy(b, 0, result, a.length, b.length); - return result; - } - - /** - * Escapes usernames or passwords for SASL. - */ - public static String saslEscape(final String s) { - final StringBuilder sb = new StringBuilder((int) (s.length() * 1.1)); - for (int i = 0; i < s.length(); i++) { - char c = s.charAt(i); - switch (c) { - case ',': - sb.append("=2C"); - break; - case '=': - sb.append("=3D"); - break; - default: - sb.append(c); - break; - } - } - return sb.toString(); - } - - public static String saslPrep(final String s) { - return Normalizer.normalize(s, Normalizer.Form.NFKC); - } - - public static String prettifyFingerprint(String fingerprint) { - if (fingerprint==null) { - return ""; - } else if (fingerprint.length() < 40) { - return fingerprint; - } - StringBuilder builder = new StringBuilder(fingerprint.toLowerCase(Locale.US).replaceAll("\\s", "")); - for(int i=8;i cipherSuites = new LinkedHashSet<>(Arrays.asList(Config.ENABLED_CIPHERS)); - final List platformCiphers = Arrays.asList(platformSupportedCipherSuites); - cipherSuites.retainAll(platformCiphers); - cipherSuites.addAll(platformCiphers); - filterWeakCipherSuites(cipherSuites); - return cipherSuites.toArray(new String[cipherSuites.size()]); - } - - private static void filterWeakCipherSuites(final Collection cipherSuites) { - final Iterator it = cipherSuites.iterator(); - while (it.hasNext()) { - String cipherName = it.next(); - // remove all ciphers with no or very weak encryption or no authentication - for (String weakCipherPattern : Config.WEAK_CIPHER_PATTERNS) { - if (cipherName.contains(weakCipherPattern)) { - it.remove(); - break; - } - } - } - } - - public static Pair extractJidAndName(X509Certificate certificate) throws CertificateEncodingException, InvalidJidException, CertificateParsingException { - Collection> alternativeNames = certificate.getSubjectAlternativeNames(); - List emails = new ArrayList<>(); - if (alternativeNames != null) { - for(List san : alternativeNames) { - Integer type = (Integer) san.get(0); - if (type == 1) { - emails.add((String) san.get(1)); - } - } - } - X500Name x500name = new JcaX509CertificateHolder(certificate).getSubject(); - if (emails.size() == 0) { - emails.add(IETFUtils.valueToString(x500name.getRDNs(BCStyle.EmailAddress)[0].getFirst().getValue())); - } - String name = IETFUtils.valueToString(x500name.getRDNs(BCStyle.CN)[0].getFirst().getValue()); - if (emails.size() >= 1) { - return new Pair<>(Jid.fromString(emails.get(0)), name); - } else { - return null; - } - } - - public static Bundle extractCertificateInformation(X509Certificate certificate) { - Bundle information = new Bundle(); - try { - JcaX509CertificateHolder holder = new JcaX509CertificateHolder(certificate); - X500Name subject = holder.getSubject(); - try { - information.putString("subject_cn", subject.getRDNs(BCStyle.CN)[0].getFirst().getValue().toString()); - } catch (Exception e) { - //ignored - } - try { - information.putString("subject_o",subject.getRDNs(BCStyle.O)[0].getFirst().getValue().toString()); - } catch (Exception e) { - //ignored - } - - X500Name issuer = holder.getIssuer(); - try { - information.putString("issuer_cn", issuer.getRDNs(BCStyle.CN)[0].getFirst().getValue().toString()); - } catch (Exception e) { - //ignored - } - try { - information.putString("issuer_o", issuer.getRDNs(BCStyle.O)[0].getFirst().getValue().toString()); - } catch (Exception e) { - //ignored - } - try { - information.putString("sha1", getFingerprintCert(certificate.getEncoded())); - } catch (Exception e) { - - } - return information; - } catch (CertificateEncodingException e) { - return information; - } - } - - public static String getFingerprintCert(byte[] input) throws NoSuchAlgorithmException { - MessageDigest md = MessageDigest.getInstance("SHA-1"); - byte[] fingerprint = md.digest(input); - return prettifyFingerprintCert(bytesToHex(fingerprint)); - } + public static final String FILETRANSFER = "?FILETRANSFERv1:"; + private final static char[] hexArray = "0123456789abcdef".toCharArray(); + + public static final Pattern UUID_PATTERN = Pattern.compile("[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"); + final public static byte[] ONE = new byte[]{0, 0, 0, 1}; + + public static String bytesToHex(byte[] bytes) { + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } + + public static byte[] hexToBytes(String hexString) { + int len = hexString.length(); + byte[] array = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + array[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character + .digit(hexString.charAt(i + 1), 16)); + } + return array; + } + + public static String hexToString(final String hexString) { + return new String(hexToBytes(hexString)); + } + + public static byte[] concatenateByteArrays(byte[] a, byte[] b) { + byte[] result = new byte[a.length + b.length]; + System.arraycopy(a, 0, result, 0, a.length); + System.arraycopy(b, 0, result, a.length, b.length); + return result; + } + + /** + * Escapes usernames or passwords for SASL. + */ + public static String saslEscape(final String s) { + final StringBuilder sb = new StringBuilder((int) (s.length() * 1.1)); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + switch (c) { + case ',': + sb.append("=2C"); + break; + case '=': + sb.append("=3D"); + break; + default: + sb.append(c); + break; + } + } + return sb.toString(); + } + + public static String saslPrep(final String s) { + return Normalizer.normalize(s, Normalizer.Form.NFKC); + } + + public static String prettifyFingerprint(String fingerprint) { + if (fingerprint == null) { + return ""; + } else if (fingerprint.length() < 40) { + return fingerprint; + } + StringBuilder builder = new StringBuilder(fingerprint.toLowerCase(Locale.US).replaceAll("\\s", "")); + for (int i = 8; i < builder.length(); i += 9) { + builder.insert(i, ' '); + } + return builder.toString(); + } + + public static String prettifyFingerprintCert(String fingerprint) { + StringBuilder builder = new StringBuilder(fingerprint); + for (int i = 2; i < builder.length(); i += 3) { + builder.insert(i, ':'); + } + return builder.toString(); + } + + public static String[] getOrderedCipherSuites(final String[] platformSupportedCipherSuites) { + final Collection cipherSuites = new LinkedHashSet<>(Arrays.asList(Config.ENABLED_CIPHERS)); + final List platformCiphers = Arrays.asList(platformSupportedCipherSuites); + cipherSuites.retainAll(platformCiphers); + cipherSuites.addAll(platformCiphers); + filterWeakCipherSuites(cipherSuites); + return cipherSuites.toArray(new String[cipherSuites.size()]); + } + + private static void filterWeakCipherSuites(final Collection cipherSuites) { + final Iterator it = cipherSuites.iterator(); + while (it.hasNext()) { + String cipherName = it.next(); + // remove all ciphers with no or very weak encryption or no authentication + for (String weakCipherPattern : Config.WEAK_CIPHER_PATTERNS) { + if (cipherName.contains(weakCipherPattern)) { + it.remove(); + break; + } + } + } + } + + public static Pair extractJidAndName(X509Certificate certificate) throws CertificateEncodingException, InvalidJidException, CertificateParsingException { + Collection> alternativeNames = certificate.getSubjectAlternativeNames(); + List emails = new ArrayList<>(); + if (alternativeNames != null) { + for (List san : alternativeNames) { + Integer type = (Integer) san.get(0); + if (type == 1) { + emails.add((String) san.get(1)); + } + } + } + X500Name x500name = new JcaX509CertificateHolder(certificate).getSubject(); + if (emails.size() == 0) { + emails.add(IETFUtils.valueToString(x500name.getRDNs(BCStyle.EmailAddress)[0].getFirst().getValue())); + } + String name = IETFUtils.valueToString(x500name.getRDNs(BCStyle.CN)[0].getFirst().getValue()); + if (emails.size() >= 1) { + return new Pair<>(Jid.fromString(emails.get(0)), name); + } else { + return null; + } + } + + public static Bundle extractCertificateInformation(X509Certificate certificate) { + Bundle information = new Bundle(); + try { + JcaX509CertificateHolder holder = new JcaX509CertificateHolder(certificate); + X500Name subject = holder.getSubject(); + try { + information.putString("subject_cn", subject.getRDNs(BCStyle.CN)[0].getFirst().getValue().toString()); + } catch (Exception e) { + //ignored + } + try { + information.putString("subject_o", subject.getRDNs(BCStyle.O)[0].getFirst().getValue().toString()); + } catch (Exception e) { + //ignored + } + + X500Name issuer = holder.getIssuer(); + try { + information.putString("issuer_cn", issuer.getRDNs(BCStyle.CN)[0].getFirst().getValue().toString()); + } catch (Exception e) { + //ignored + } + try { + information.putString("issuer_o", issuer.getRDNs(BCStyle.O)[0].getFirst().getValue().toString()); + } catch (Exception e) { + //ignored + } + try { + information.putString("sha1", getFingerprintCert(certificate.getEncoded())); + } catch (Exception e) { + + } + return information; + } catch (CertificateEncodingException e) { + return information; + } + } + + public static String getFingerprintCert(byte[] input) throws NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + byte[] fingerprint = md.digest(input); + return prettifyFingerprintCert(bytesToHex(fingerprint)); + } public static String getAccountFingerprint(Account account) { try { @@ -214,16 +214,16 @@ public final class CryptoHelper { } } - public static int encryptionTypeToText(int encryption) { - switch (encryption) { - case Message.ENCRYPTION_OTR: - return R.string.encryption_choice_otr; - case Message.ENCRYPTION_AXOLOTL: - return R.string.encryption_choice_omemo; - case Message.ENCRYPTION_NONE: - return R.string.encryption_choice_unencrypted; - default: - return R.string.encryption_choice_pgp; - } - } + public static int encryptionTypeToText(int encryption) { + switch (encryption) { + case Message.ENCRYPTION_OTR: + return R.string.encryption_choice_otr; + case Message.ENCRYPTION_AXOLOTL: + return R.string.encryption_choice_omemo; + case Message.ENCRYPTION_NONE: + return R.string.encryption_choice_unencrypted; + default: + return R.string.encryption_choice_pgp; + } + } } diff --git a/src/main/java/de/pixart/messenger/utils/DNSHelper.java b/src/main/java/de/pixart/messenger/utils/DNSHelper.java index 4a692c910..70f600375 100644 --- a/src/main/java/de/pixart/messenger/utils/DNSHelper.java +++ b/src/main/java/de/pixart/messenger/utils/DNSHelper.java @@ -40,260 +40,260 @@ import de.pixart.messenger.xmpp.jid.Jid; public class DNSHelper { - public static final Pattern PATTERN_IPV4 = Pattern.compile("\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); - public static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); - public static final Pattern PATTERN_IPV6_6HEX4DEC = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); - public static final Pattern PATTERN_IPV6_HEXCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z"); - public static final Pattern PATTERN_IPV6 = Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z"); + public static final Pattern PATTERN_IPV4 = Pattern.compile("\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); + public static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); + public static final Pattern PATTERN_IPV6_6HEX4DEC = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); + public static final Pattern PATTERN_IPV6_HEXCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z"); + public static final Pattern PATTERN_IPV6 = Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z"); - protected static Client client = new Client(); + protected static Client client = new Client(); - protected static Context context; + protected static Context context; - public static Bundle getSRVRecord(final Jid jid, Context context) throws IOException { - DNSHelper.context = context; + public static Bundle getSRVRecord(final Jid jid, Context context) throws IOException { + DNSHelper.context = context; final String host = jid.getDomainpart(); - final List servers = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? getDnsServers(context) : getDnsServersPreLollipop(); - Bundle b = new Bundle(); - boolean interrupted = false; - for(InetAddress server : servers) { - if (Thread.currentThread().isInterrupted()) { - interrupted = true; - break; - } - b = queryDNS(host, server); - if (b.containsKey("values")) { - return b; - } - } - if (!b.containsKey("values")) { - Log.d(Config.LOGTAG,(interrupted ? "Thread interrupted during DNS query" :"all dns queries failed") + ". provide fallback A record"); - ArrayList values = new ArrayList<>(); - values.add(createNamePortBundle(host, 5222, false)); - b.putParcelableArrayList("values",values); - } - return b; - } + final List servers = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? getDnsServers(context) : getDnsServersPreLollipop(); + Bundle b = new Bundle(); + boolean interrupted = false; + for (InetAddress server : servers) { + if (Thread.currentThread().isInterrupted()) { + interrupted = true; + break; + } + b = queryDNS(host, server); + if (b.containsKey("values")) { + return b; + } + } + if (!b.containsKey("values")) { + Log.d(Config.LOGTAG, (interrupted ? "Thread interrupted during DNS query" : "all dns queries failed") + ". provide fallback A record"); + ArrayList values = new ArrayList<>(); + values.add(createNamePortBundle(host, 5222, false)); + b.putParcelableArrayList("values", values); + } + return b; + } - @TargetApi(21) - private static List getDnsServers(Context context) { - List servers = new ArrayList<>(); - ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - Network[] networks = connectivityManager == null ? null : connectivityManager.getAllNetworks(); - if (networks == null) { - return getDnsServersPreLollipop(); - } - for(int i = 0; i < networks.length; ++i) { - LinkProperties linkProperties = connectivityManager.getLinkProperties(networks[i]); - if (linkProperties != null) { - if (hasDefaultRoute(linkProperties)) { - servers.addAll(0, getIPv4First(linkProperties.getDnsServers())); - } else { - servers.addAll(getIPv4First(linkProperties.getDnsServers())); - } - } - } - if (servers.size() > 0) { - Log.d(Config.LOGTAG, "used lollipop variant to discover dns servers in " + networks.length + " networks"); - } - return servers.size() > 0 ? servers : getDnsServersPreLollipop(); - } + @TargetApi(21) + private static List getDnsServers(Context context) { + List servers = new ArrayList<>(); + ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + Network[] networks = connectivityManager == null ? null : connectivityManager.getAllNetworks(); + if (networks == null) { + return getDnsServersPreLollipop(); + } + for (int i = 0; i < networks.length; ++i) { + LinkProperties linkProperties = connectivityManager.getLinkProperties(networks[i]); + if (linkProperties != null) { + if (hasDefaultRoute(linkProperties)) { + servers.addAll(0, getIPv4First(linkProperties.getDnsServers())); + } else { + servers.addAll(getIPv4First(linkProperties.getDnsServers())); + } + } + } + if (servers.size() > 0) { + Log.d(Config.LOGTAG, "used lollipop variant to discover dns servers in " + networks.length + " networks"); + } + return servers.size() > 0 ? servers : getDnsServersPreLollipop(); + } - private static List getIPv4First(List in) { - List out = new ArrayList<>(); - for(InetAddress addr : in) { - if (addr instanceof Inet4Address) { - out.add(0, addr); - } else { - out.add(addr); - } - } - return out; - } + private static List getIPv4First(List in) { + List out = new ArrayList<>(); + for (InetAddress addr : in) { + if (addr instanceof Inet4Address) { + out.add(0, addr); + } else { + out.add(addr); + } + } + return out; + } - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private static boolean hasDefaultRoute(LinkProperties linkProperties) { - for(RouteInfo route: linkProperties.getRoutes()) { - if (route.isDefaultRoute()) { - return true; - } - } - return false; - } + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + private static boolean hasDefaultRoute(LinkProperties linkProperties) { + for (RouteInfo route : linkProperties.getRoutes()) { + if (route.isDefaultRoute()) { + return true; + } + } + return false; + } - private static List getDnsServersPreLollipop() { - List servers = new ArrayList<>(); - String[] dns = client.findDNS(); - for(int i = 0; i < dns.length; ++i) { - try { - servers.add(InetAddress.getByName(dns[i])); - } catch (UnknownHostException e) { - //ignore - } - } - return servers; - } + private static List getDnsServersPreLollipop() { + List servers = new ArrayList<>(); + String[] dns = client.findDNS(); + for (int i = 0; i < dns.length; ++i) { + try { + servers.add(InetAddress.getByName(dns[i])); + } catch (UnknownHostException e) { + //ignore + } + } + return servers; + } - private static class TlsSrv { - private final SRV srv; - private final boolean tls; + private static class TlsSrv { + private final SRV srv; + private final boolean tls; - public TlsSrv(SRV srv, boolean tls) { - this.srv = srv; - this.tls = tls; - } - } + public TlsSrv(SRV srv, boolean tls) { + this.srv = srv; + this.tls = tls; + } + } - private static void fillSrvMaps(final String qname, final InetAddress dnsServer, final Map> priorities, final Map> ips4, final Map> ips6, final boolean tls) throws IOException { - final DNSMessage message = client.query(qname, TYPE.SRV, CLASS.IN, dnsServer.getHostAddress()); - for (Record[] rrset : new Record[][] { message.getAnswers(), message.getAdditionalResourceRecords() }) { - for (Record rr : rrset) { - Data d = rr.getPayload(); + private static void fillSrvMaps(final String qname, final InetAddress dnsServer, final Map> priorities, final Map> ips4, final Map> ips6, final boolean tls) throws IOException { + final DNSMessage message = client.query(qname, TYPE.SRV, CLASS.IN, dnsServer.getHostAddress()); + for (Record[] rrset : new Record[][]{message.getAnswers(), message.getAdditionalResourceRecords()}) { + for (Record rr : rrset) { + Data d = rr.getPayload(); final String name = rr.getName() != null ? rr.getName().toLowerCase(Locale.US) : null; if (d instanceof SRV && NameUtil.idnEquals(qname, name)) { - SRV srv = (SRV) d; - if (!priorities.containsKey(srv.getPriority())) { - priorities.put(srv.getPriority(),new ArrayList()); - } - priorities.get(srv.getPriority()).add(new TlsSrv(srv, tls)); + SRV srv = (SRV) d; + if (!priorities.containsKey(srv.getPriority())) { + priorities.put(srv.getPriority(), new ArrayList()); + } + priorities.get(srv.getPriority()).add(new TlsSrv(srv, tls)); } else if (d instanceof SRV) { - Log.d(Config.LOGTAG,"found unrecognized SRV record with name: "+name); - } - if (d instanceof A) { - A a = (A) d; - if (!ips4.containsKey(name)) { - ips4.put(name, new ArrayList()); - } - ips4.get(name).add(a.toString()); - } - if (d instanceof AAAA) { - AAAA aaaa = (AAAA) d; - if (!ips6.containsKey(name)) { - ips6.put(name, new ArrayList()); - } - ips6.get(name).add("[" + aaaa.toString() + "]"); - } - } - } - } + Log.d(Config.LOGTAG, "found unrecognized SRV record with name: " + name); + } + if (d instanceof A) { + A a = (A) d; + if (!ips4.containsKey(name)) { + ips4.put(name, new ArrayList()); + } + ips4.get(name).add(a.toString()); + } + if (d instanceof AAAA) { + AAAA aaaa = (AAAA) d; + if (!ips6.containsKey(name)) { + ips6.put(name, new ArrayList()); + } + ips6.get(name).add("[" + aaaa.toString() + "]"); + } + } + } + } - public static Bundle queryDNS(String host, InetAddress dnsServer) { - Bundle bundle = new Bundle(); - try { - client.setTimeout(Config.SOCKET_TIMEOUT * 1000); - final String qname = "_xmpp-client._tcp." + host.toLowerCase(Locale.US); - final String tlsQname = "_xmpps-client._tcp." + host.toLowerCase(Locale.US); - Log.d(Config.LOGTAG, "using dns server: " + dnsServer.getHostAddress() + " to look up " + host); + public static Bundle queryDNS(String host, InetAddress dnsServer) { + Bundle bundle = new Bundle(); + try { + client.setTimeout(Config.SOCKET_TIMEOUT * 1000); + final String qname = "_xmpp-client._tcp." + host.toLowerCase(Locale.US); + final String tlsQname = "_xmpps-client._tcp." + host.toLowerCase(Locale.US); + Log.d(Config.LOGTAG, "using dns server: " + dnsServer.getHostAddress() + " to look up " + host); - final Map> priorities = new TreeMap<>(); - final Map> ips4 = new TreeMap<>(); - final Map> ips6 = new TreeMap<>(); + final Map> priorities = new TreeMap<>(); + final Map> ips4 = new TreeMap<>(); + final Map> ips6 = new TreeMap<>(); - fillSrvMaps(qname, dnsServer, priorities, ips4, ips6, false); - fillSrvMaps(tlsQname, dnsServer, priorities, ips4, ips6, true); + fillSrvMaps(qname, dnsServer, priorities, ips4, ips6, false); + fillSrvMaps(tlsQname, dnsServer, priorities, ips4, ips6, true); - final List result = new ArrayList<>(); - for (final List s : priorities.values()) { - result.addAll(s); - } + final List result = new ArrayList<>(); + for (final List s : priorities.values()) { + result.addAll(s); + } - final ArrayList values = new ArrayList<>(); - if (result.size() == 0) { - DNSMessage response; - try { - response = client.query(host, TYPE.A, CLASS.IN, dnsServer.getHostAddress()); - for (int i = 0; i < response.getAnswers().length; ++i) { - values.add(createNamePortBundle(host, 5222, response.getAnswers()[i].getPayload(), false)); - } - } catch (SocketTimeoutException e) { - Log.d(Config.LOGTAG,"ignoring timeout exception when querying A record on "+dnsServer.getHostAddress()); - } - try { - response = client.query(host, TYPE.AAAA, CLASS.IN, dnsServer.getHostAddress()); - for (int i = 0; i < response.getAnswers().length; ++i) { - values.add(createNamePortBundle(host, 5222, response.getAnswers()[i].getPayload(), false)); - } - } catch (SocketTimeoutException e) { - Log.d(Config.LOGTAG,"ignoring timeout exception when querying AAAA record on "+dnsServer.getHostAddress()); - } - values.add(createNamePortBundle(host, 5222, false)); - bundle.putParcelableArrayList("values", values); - return bundle; - } - for (final TlsSrv tlsSrv : result) { - final SRV srv = tlsSrv.srv; + final ArrayList values = new ArrayList<>(); + if (result.size() == 0) { + DNSMessage response; + try { + response = client.query(host, TYPE.A, CLASS.IN, dnsServer.getHostAddress()); + for (int i = 0; i < response.getAnswers().length; ++i) { + values.add(createNamePortBundle(host, 5222, response.getAnswers()[i].getPayload(), false)); + } + } catch (SocketTimeoutException e) { + Log.d(Config.LOGTAG, "ignoring timeout exception when querying A record on " + dnsServer.getHostAddress()); + } + try { + response = client.query(host, TYPE.AAAA, CLASS.IN, dnsServer.getHostAddress()); + for (int i = 0; i < response.getAnswers().length; ++i) { + values.add(createNamePortBundle(host, 5222, response.getAnswers()[i].getPayload(), false)); + } + } catch (SocketTimeoutException e) { + Log.d(Config.LOGTAG, "ignoring timeout exception when querying AAAA record on " + dnsServer.getHostAddress()); + } + values.add(createNamePortBundle(host, 5222, false)); + bundle.putParcelableArrayList("values", values); + return bundle; + } + for (final TlsSrv tlsSrv : result) { + final SRV srv = tlsSrv.srv; final String name = srv.getName() != null ? srv.getName().toLowerCase(Locale.US) : null; - if (ips6.containsKey(name)) { - values.add(createNamePortBundle(name,srv.getPort(),ips6, tlsSrv.tls)); - } else { - try { - DNSMessage response = client.query(name, TYPE.AAAA, CLASS.IN, dnsServer.getHostAddress()); - for (int i = 0; i < response.getAnswers().length; ++i) { - values.add(createNamePortBundle(name, srv.getPort(), response.getAnswers()[i].getPayload(), tlsSrv.tls)); - } - } catch (SocketTimeoutException e) { - Log.d(Config.LOGTAG,"ignoring timeout exception when querying AAAA record on "+dnsServer.getHostAddress()); - } - } - if (ips4.containsKey(name)) { - values.add(createNamePortBundle(name,srv.getPort(),ips4, tlsSrv.tls)); - } else { - DNSMessage response = client.query(name, TYPE.A, CLASS.IN, dnsServer.getHostAddress()); - for(int i = 0; i < response.getAnswers().length; ++i) { - values.add(createNamePortBundle(name,srv.getPort(),response.getAnswers()[i].getPayload(), tlsSrv.tls)); - } - } - values.add(createNamePortBundle(name, srv.getPort(), tlsSrv.tls)); - } - bundle.putParcelableArrayList("values", values); - } catch (SocketTimeoutException e) { - bundle.putString("error", "timeout"); - } catch (Exception e) { - bundle.putString("error", "unhandled"); - } - return bundle; - } + if (ips6.containsKey(name)) { + values.add(createNamePortBundle(name, srv.getPort(), ips6, tlsSrv.tls)); + } else { + try { + DNSMessage response = client.query(name, TYPE.AAAA, CLASS.IN, dnsServer.getHostAddress()); + for (int i = 0; i < response.getAnswers().length; ++i) { + values.add(createNamePortBundle(name, srv.getPort(), response.getAnswers()[i].getPayload(), tlsSrv.tls)); + } + } catch (SocketTimeoutException e) { + Log.d(Config.LOGTAG, "ignoring timeout exception when querying AAAA record on " + dnsServer.getHostAddress()); + } + } + if (ips4.containsKey(name)) { + values.add(createNamePortBundle(name, srv.getPort(), ips4, tlsSrv.tls)); + } else { + DNSMessage response = client.query(name, TYPE.A, CLASS.IN, dnsServer.getHostAddress()); + for (int i = 0; i < response.getAnswers().length; ++i) { + values.add(createNamePortBundle(name, srv.getPort(), response.getAnswers()[i].getPayload(), tlsSrv.tls)); + } + } + values.add(createNamePortBundle(name, srv.getPort(), tlsSrv.tls)); + } + bundle.putParcelableArrayList("values", values); + } catch (SocketTimeoutException e) { + bundle.putString("error", "timeout"); + } catch (Exception e) { + bundle.putString("error", "unhandled"); + } + return bundle; + } - private static Bundle createNamePortBundle(String name, int port, final boolean tls) { - Bundle namePort = new Bundle(); - namePort.putString("name", name); - namePort.putBoolean("tls", tls); - namePort.putInt("port", port); - return namePort; - } + private static Bundle createNamePortBundle(String name, int port, final boolean tls) { + Bundle namePort = new Bundle(); + namePort.putString("name", name); + namePort.putBoolean("tls", tls); + namePort.putInt("port", port); + return namePort; + } - private static Bundle createNamePortBundle(String name, int port, Map> ips, final boolean tls) { - Bundle namePort = new Bundle(); - namePort.putString("name", name); - namePort.putBoolean("tls", tls); - namePort.putInt("port", port); - if (ips!=null) { - List ip = ips.get(name); - Collections.shuffle(ip, new Random()); - namePort.putString("ip", ip.get(0)); - } - return namePort; - } + private static Bundle createNamePortBundle(String name, int port, Map> ips, final boolean tls) { + Bundle namePort = new Bundle(); + namePort.putString("name", name); + namePort.putBoolean("tls", tls); + namePort.putInt("port", port); + if (ips != null) { + List ip = ips.get(name); + Collections.shuffle(ip, new Random()); + namePort.putString("ip", ip.get(0)); + } + return namePort; + } - private static Bundle createNamePortBundle(String name, int port, Data data, final boolean tls) { - Bundle namePort = new Bundle(); - namePort.putString("name", name); - namePort.putBoolean("tls", tls); - namePort.putInt("port", port); - if (data instanceof A) { - namePort.putString("ip", data.toString()); - } else if (data instanceof AAAA) { - namePort.putString("ip","["+data.toString()+"]"); - } - return namePort; - } + private static Bundle createNamePortBundle(String name, int port, Data data, final boolean tls) { + Bundle namePort = new Bundle(); + namePort.putString("name", name); + namePort.putBoolean("tls", tls); + namePort.putInt("port", port); + if (data instanceof A) { + namePort.putString("ip", data.toString()); + } else if (data instanceof AAAA) { + namePort.putString("ip", "[" + data.toString() + "]"); + } + return namePort; + } - public static boolean isIp(final String server) { - return server != null && ( - PATTERN_IPV4.matcher(server).matches() - || PATTERN_IPV6.matcher(server).matches() - || PATTERN_IPV6_6HEX4DEC.matcher(server).matches() - || PATTERN_IPV6_HEX4DECCOMPRESSED.matcher(server).matches() - || PATTERN_IPV6_HEXCOMPRESSED.matcher(server).matches()); - } + public static boolean isIp(final String server) { + return server != null && ( + PATTERN_IPV4.matcher(server).matches() + || PATTERN_IPV6.matcher(server).matches() + || PATTERN_IPV6_6HEX4DEC.matcher(server).matches() + || PATTERN_IPV6_HEX4DECCOMPRESSED.matcher(server).matches() + || PATTERN_IPV6_HEXCOMPRESSED.matcher(server).matches()); + } } diff --git a/src/main/java/de/pixart/messenger/utils/ExceptionHandler.java b/src/main/java/de/pixart/messenger/utils/ExceptionHandler.java index 40648dc04..ec38ea754 100644 --- a/src/main/java/de/pixart/messenger/utils/ExceptionHandler.java +++ b/src/main/java/de/pixart/messenger/utils/ExceptionHandler.java @@ -9,23 +9,23 @@ import java.lang.Thread.UncaughtExceptionHandler; public class ExceptionHandler implements UncaughtExceptionHandler { - private UncaughtExceptionHandler defaultHandler; - private Context context; + private UncaughtExceptionHandler defaultHandler; + private Context context; - public ExceptionHandler(Context context) { - this.context = context; - this.defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); - } + public ExceptionHandler(Context context) { + this.context = context; + this.defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); + } - @Override - public void uncaughtException(Thread thread, Throwable ex) { - Writer result = new StringWriter(); - PrintWriter printWriter = new PrintWriter(result); - ex.printStackTrace(printWriter); - String stacktrace = result.toString(); - printWriter.close(); - ExceptionHelper.writeToStacktraceFile(context, stacktrace); - this.defaultHandler.uncaughtException(thread, ex); - } + @Override + public void uncaughtException(Thread thread, Throwable ex) { + Writer result = new StringWriter(); + PrintWriter printWriter = new PrintWriter(result); + ex.printStackTrace(printWriter); + String stacktrace = result.toString(); + printWriter.close(); + ExceptionHelper.writeToStacktraceFile(context, stacktrace); + this.defaultHandler.uncaughtException(thread, ex); + } } diff --git a/src/main/java/de/pixart/messenger/utils/ExceptionHelper.java b/src/main/java/de/pixart/messenger/utils/ExceptionHelper.java index b405f24d4..5cf46c151 100644 --- a/src/main/java/de/pixart/messenger/utils/ExceptionHelper.java +++ b/src/main/java/de/pixart/messenger/utils/ExceptionHelper.java @@ -31,107 +31,108 @@ import de.pixart.messenger.xmpp.jid.InvalidJidException; import de.pixart.messenger.xmpp.jid.Jid; public class ExceptionHelper { - private static SimpleDateFormat DATE_FORMATs = new SimpleDateFormat("yyyy-MM-dd"); - public static void init(Context context) { - if (!(Thread.getDefaultUncaughtExceptionHandler() instanceof ExceptionHandler)) { - Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler( - context)); - } - } + private static SimpleDateFormat DATE_FORMATs = new SimpleDateFormat("yyyy-MM-dd"); - public static boolean checkForCrash(ConversationActivity activity, final XmppConnectionService service) { - try { - final SharedPreferences preferences = PreferenceManager - .getDefaultSharedPreferences(activity); - boolean crashreport = preferences.getBoolean("crashreport", true); - if (!crashreport || Config.BUG_REPORTS == null) { - return false; - } - List accounts = service.getAccounts(); - Account account = null; - for (int i = 0; i < accounts.size(); ++i) { - if (!accounts.get(i).isOptionSet(Account.OPTION_DISABLED)) { - account = accounts.get(i); - break; - } - } - if (account == null) { - return false; - } - final Account finalAccount = account; - FileInputStream file = activity.openFileInput("stacktrace.txt"); - InputStreamReader inputStreamReader = new InputStreamReader(file); - BufferedReader stacktrace = new BufferedReader(inputStreamReader); - final StringBuilder report = new StringBuilder(); - PackageManager pm = activity.getPackageManager(); - PackageInfo packageInfo; - try { - packageInfo = pm.getPackageInfo(activity.getPackageName(), PackageManager.GET_SIGNATURES); - report.append("Version: " + packageInfo.versionName + '\n'); - report.append("Last Update: " + DATE_FORMATs.format(new Date(packageInfo.lastUpdateTime)) + '\n'); - Signature[] signatures = packageInfo.signatures; - if (signatures != null && signatures.length >= 1) { - report.append("SHA-1: " + CryptoHelper.getFingerprintCert(packageInfo.signatures[0].toByteArray()) + "\n"); - } - report.append('\n'); - } catch (Exception e) { - e.printStackTrace(); - return false; - } - String line; - while ((line = stacktrace.readLine()) != null) { - report.append(line); - report.append('\n'); - } - file.close(); - activity.deleteFile("stacktrace.txt"); - AlertDialog.Builder builder = new AlertDialog.Builder(activity); - builder.setTitle(activity.getString(R.string.crash_report_title)); - builder.setMessage(activity.getText(R.string.crash_report_message)); - builder.setPositiveButton(activity.getText(R.string.send_now), - new OnClickListener() { + public static void init(Context context) { + if (!(Thread.getDefaultUncaughtExceptionHandler() instanceof ExceptionHandler)) { + Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler( + context)); + } + } - @Override - public void onClick(DialogInterface dialog, int which) { + public static boolean checkForCrash(ConversationActivity activity, final XmppConnectionService service) { + try { + final SharedPreferences preferences = PreferenceManager + .getDefaultSharedPreferences(activity); + boolean crashreport = preferences.getBoolean("crashreport", true); + if (!crashreport || Config.BUG_REPORTS == null) { + return false; + } + List accounts = service.getAccounts(); + Account account = null; + for (int i = 0; i < accounts.size(); ++i) { + if (!accounts.get(i).isOptionSet(Account.OPTION_DISABLED)) { + account = accounts.get(i); + break; + } + } + if (account == null) { + return false; + } + final Account finalAccount = account; + FileInputStream file = activity.openFileInput("stacktrace.txt"); + InputStreamReader inputStreamReader = new InputStreamReader(file); + BufferedReader stacktrace = new BufferedReader(inputStreamReader); + final StringBuilder report = new StringBuilder(); + PackageManager pm = activity.getPackageManager(); + PackageInfo packageInfo; + try { + packageInfo = pm.getPackageInfo(activity.getPackageName(), PackageManager.GET_SIGNATURES); + report.append("Version: " + packageInfo.versionName + '\n'); + report.append("Last Update: " + DATE_FORMATs.format(new Date(packageInfo.lastUpdateTime)) + '\n'); + Signature[] signatures = packageInfo.signatures; + if (signatures != null && signatures.length >= 1) { + report.append("SHA-1: " + CryptoHelper.getFingerprintCert(packageInfo.signatures[0].toByteArray()) + "\n"); + } + report.append('\n'); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + String line; + while ((line = stacktrace.readLine()) != null) { + report.append(line); + report.append('\n'); + } + file.close(); + activity.deleteFile("stacktrace.txt"); + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setTitle(activity.getString(R.string.crash_report_title)); + builder.setMessage(activity.getText(R.string.crash_report_message)); + builder.setPositiveButton(activity.getText(R.string.send_now), + new OnClickListener() { - Log.d(Config.LOGTAG, "using account=" - + finalAccount.getJid().toBareJid() - + " to send in stack trace"); + @Override + public void onClick(DialogInterface dialog, int which) { - Conversation conversation = null; - try { - conversation = service.findOrCreateConversation(finalAccount, - Jid.fromString(Config.BUG_REPORTS), false); - } catch (final InvalidJidException ignored) { - } - Message message = new Message(conversation, report - .toString(), Message.ENCRYPTION_NONE); - service.sendMessage(message); - } - }); - builder.setNegativeButton(activity.getText(R.string.send_never), - new OnClickListener() { + Log.d(Config.LOGTAG, "using account=" + + finalAccount.getJid().toBareJid() + + " to send in stack trace"); - @Override - public void onClick(DialogInterface dialog, int which) { - preferences.edit().putBoolean("crash_report", false) - .apply(); - } - }); - builder.create().show(); - return true; - } catch (final IOException ignored) { - return false; - } - } + Conversation conversation = null; + try { + conversation = service.findOrCreateConversation(finalAccount, + Jid.fromString(Config.BUG_REPORTS), false); + } catch (final InvalidJidException ignored) { + } + Message message = new Message(conversation, report + .toString(), Message.ENCRYPTION_NONE); + service.sendMessage(message); + } + }); + builder.setNegativeButton(activity.getText(R.string.send_never), + new OnClickListener() { - public static void writeToStacktraceFile(Context context, String msg) { - try { - OutputStream os = context.openFileOutput("stacktrace.txt", Context.MODE_PRIVATE); - os.write(msg.getBytes()); - os.flush(); - os.close(); - } catch (IOException ignored) { - } - } + @Override + public void onClick(DialogInterface dialog, int which) { + preferences.edit().putBoolean("crash_report", false) + .apply(); + } + }); + builder.create().show(); + return true; + } catch (final IOException ignored) { + return false; + } + } + + public static void writeToStacktraceFile(Context context, String msg) { + try { + OutputStream os = context.openFileOutput("stacktrace.txt", Context.MODE_PRIVATE); + os.write(msg.getBytes()); + os.flush(); + os.close(); + } catch (IOException ignored) { + } + } } diff --git a/src/main/java/de/pixart/messenger/utils/ExifHelper.java b/src/main/java/de/pixart/messenger/utils/ExifHelper.java index 0ac4d6e4d..230af54d1 100644 --- a/src/main/java/de/pixart/messenger/utils/ExifHelper.java +++ b/src/main/java/de/pixart/messenger/utils/ExifHelper.java @@ -66,7 +66,7 @@ public class ExifHelper { if (!read(is, buf, 6)) return 0; length -= 6; if (pack(buf, 0, 4, false) == 0x45786966 && - pack(buf, 4, 2, false) == 0) { + pack(buf, 4, 2, false) == 0) { break; } } @@ -136,7 +136,7 @@ public class ExifHelper { } private static int pack(byte[] bytes, int offset, int length, - boolean littleEndian) { + boolean littleEndian) { int step = 1; if (littleEndian) { offset += length - 1; diff --git a/src/main/java/de/pixart/messenger/utils/FileUtils.java b/src/main/java/de/pixart/messenger/utils/FileUtils.java index fa69de49b..1500b6246 100644 --- a/src/main/java/de/pixart/messenger/utils/FileUtils.java +++ b/src/main/java/de/pixart/messenger/utils/FileUtils.java @@ -14,145 +14,145 @@ import java.io.File; public class FileUtils { - /** - * Get a file path from a Uri. This will get the the path for Storage Access - * Framework Documents, as well as the _data field for the MediaStore and - * other file-based ContentProviders. - * - * @param context The context. - * @param uri The Uri to query. - * @author paulburke - */ - @SuppressLint("NewApi") - public static String getPath(final Context context, final Uri uri) { - if (uri == null) { - return null; - } - - final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; - - // DocumentProvider - if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { - // ExternalStorageProvider - if (isExternalStorageDocument(uri)) { - final String docId = DocumentsContract.getDocumentId(uri); - final String[] split = docId.split(":"); - final String type = split[0]; - - if ("primary".equalsIgnoreCase(type)) { - return Environment.getExternalStorageDirectory() + "/" + split[1]; - } - - // TODO handle non-primary volumes - } - // DownloadsProvider - else if (isDownloadsDocument(uri)) { - - final String id = DocumentsContract.getDocumentId(uri); - final Uri contentUri = ContentUris.withAppendedId( - Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); - - return getDataColumn(context, contentUri, null, null); - } - // MediaProvider - else if (isMediaDocument(uri)) { - final String docId = DocumentsContract.getDocumentId(uri); - final String[] split = docId.split(":"); - final String type = split[0]; - - Uri contentUri = null; - if ("image".equals(type)) { - contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; - } else if ("video".equals(type)) { - contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; - } else if ("audio".equals(type)) { - contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; - } - - final String selection = "_id=?"; - final String[] selectionArgs = new String[]{ - split[1] - }; - - return getDataColumn(context, contentUri, selection, selectionArgs); - } - } - // MediaStore (and general) - else if ("content".equalsIgnoreCase(uri.getScheme())) { - String path = getDataColumn(context, uri, null, null); - if (path != null) { - File file = new File(path); - if (!file.canRead()) { - return null; - } - } - return path; - } - // File - else if ("file".equalsIgnoreCase(uri.getScheme())) { - return uri.getPath(); - } - - return null; - } - - /** - * Get the value of the data column for this Uri. This is useful for - * MediaStore Uris, and other file-based ContentProviders. - * - * @param context The context. - * @param uri The Uri to query. - * @param selection (Optional) Filter used in the query. - * @param selectionArgs (Optional) Selection arguments used in the query. - * @return The value of the _data column, which is typically a file path. - */ - public static String getDataColumn(Context context, Uri uri, String selection, - String[] selectionArgs) { - - Cursor cursor = null; - final String column = "_data"; - final String[] projection = { - column - }; - - try { - cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,null); - if (cursor != null && cursor.moveToFirst()) { - final int column_index = cursor.getColumnIndexOrThrow(column); - return cursor.getString(column_index); - } - } catch(Exception e) { - return null; - } finally { - if (cursor != null) { - cursor.close(); - } - } - return null; - } - - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is ExternalStorageProvider. - */ - public static boolean isExternalStorageDocument(Uri uri) { - return "com.android.externalstorage.documents".equals(uri.getAuthority()); - } - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is DownloadsProvider. - */ - public static boolean isDownloadsDocument(Uri uri) { - return "com.android.providers.downloads.documents".equals(uri.getAuthority()); - } - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is MediaProvider. - */ - public static boolean isMediaDocument(Uri uri) { - return "com.android.providers.media.documents".equals(uri.getAuthority()); - } + /** + * Get a file path from a Uri. This will get the the path for Storage Access + * Framework Documents, as well as the _data field for the MediaStore and + * other file-based ContentProviders. + * + * @param context The context. + * @param uri The Uri to query. + * @author paulburke + */ + @SuppressLint("NewApi") + public static String getPath(final Context context, final Uri uri) { + if (uri == null) { + return null; + } + + final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + + // DocumentProvider + if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { + // ExternalStorageProvider + if (isExternalStorageDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + if ("primary".equalsIgnoreCase(type)) { + return Environment.getExternalStorageDirectory() + "/" + split[1]; + } + + // TODO handle non-primary volumes + } + // DownloadsProvider + else if (isDownloadsDocument(uri)) { + + final String id = DocumentsContract.getDocumentId(uri); + final Uri contentUri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); + + return getDataColumn(context, contentUri, null, null); + } + // MediaProvider + else if (isMediaDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + Uri contentUri = null; + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + + final String selection = "_id=?"; + final String[] selectionArgs = new String[]{ + split[1] + }; + + return getDataColumn(context, contentUri, selection, selectionArgs); + } + } + // MediaStore (and general) + else if ("content".equalsIgnoreCase(uri.getScheme())) { + String path = getDataColumn(context, uri, null, null); + if (path != null) { + File file = new File(path); + if (!file.canRead()) { + return null; + } + } + return path; + } + // File + else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + + return null; + } + + /** + * Get the value of the data column for this Uri. This is useful for + * MediaStore Uris, and other file-based ContentProviders. + * + * @param context The context. + * @param uri The Uri to query. + * @param selection (Optional) Filter used in the query. + * @param selectionArgs (Optional) Selection arguments used in the query. + * @return The value of the _data column, which is typically a file path. + */ + public static String getDataColumn(Context context, Uri uri, String selection, + String[] selectionArgs) { + + Cursor cursor = null; + final String column = "_data"; + final String[] projection = { + column + }; + + try { + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); + if (cursor != null && cursor.moveToFirst()) { + final int column_index = cursor.getColumnIndexOrThrow(column); + return cursor.getString(column_index); + } + } catch (Exception e) { + return null; + } finally { + if (cursor != null) { + cursor.close(); + } + } + return null; + } + + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is ExternalStorageProvider. + */ + public static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is DownloadsProvider. + */ + public static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is MediaProvider. + */ + public static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } } diff --git a/src/main/java/de/pixart/messenger/utils/GeoHelper.java b/src/main/java/de/pixart/messenger/utils/GeoHelper.java index 0fe63e095..8f263c593 100644 --- a/src/main/java/de/pixart/messenger/utils/GeoHelper.java +++ b/src/main/java/de/pixart/messenger/utils/GeoHelper.java @@ -14,13 +14,13 @@ import de.pixart.messenger.entities.Conversation; import de.pixart.messenger.entities.Message; public class GeoHelper { - public static Pattern GEO_URI = Pattern.compile("geo:([\\-0-9.]+),([\\-0-9.]+)(?:,([\\-0-9.]+))?(?:\\?(.*))?", Pattern.CASE_INSENSITIVE); + public static Pattern GEO_URI = Pattern.compile("geo:([\\-0-9.]+),([\\-0-9.]+)(?:,([\\-0-9.]+))?(?:\\?(.*))?", Pattern.CASE_INSENSITIVE); - public static boolean isGeoUri(String body) { - return body != null && GEO_URI.matcher(body).matches(); - } + public static boolean isGeoUri(String body) { + return body != null && GEO_URI.matcher(body).matches(); + } - public static String MapPreviewUri (Message message) { + public static String MapPreviewUri(Message message) { Matcher matcher = GEO_URI.matcher(message.getBody()); if (!matcher.matches()) { return null; @@ -42,62 +42,62 @@ public class GeoHelper { return "https://maps.google.com/maps/api/staticmap?center=" + latitude + "," + longitude + "&size=500x500&scale=2&format=jpg&markers=" + latitude + "," + longitude + "&sensor=false"; } - public static ArrayList createGeoIntentsFromMessage(Message message) { - final ArrayList intents = new ArrayList<>(); - Matcher matcher = GEO_URI.matcher(message.getBody()); - if (!matcher.matches()) { - return intents; - } - double latitude; - double longitude; - try { - latitude = Double.parseDouble(matcher.group(1)); - if (latitude > 90.0 || latitude < -90.0) { - return intents; - } - longitude = Double.parseDouble(matcher.group(2)); - if (longitude > 180.0 || longitude < -180.0) { - return intents; - } - } catch (NumberFormatException nfe) { - return intents; - } - final Conversation conversation = message.getConversation(); - String label; - if (conversation.getMode() == Conversation.MODE_SINGLE && message.getStatus() == Message.STATUS_RECEIVED) { - try { - label = "(" + URLEncoder.encode(message.getConversation().getName(), "UTF-8") + ")"; - } catch (UnsupportedEncodingException e) { - label = ""; - } - } else { - label = ""; - } + public static ArrayList createGeoIntentsFromMessage(Message message) { + final ArrayList intents = new ArrayList<>(); + Matcher matcher = GEO_URI.matcher(message.getBody()); + if (!matcher.matches()) { + return intents; + } + double latitude; + double longitude; + try { + latitude = Double.parseDouble(matcher.group(1)); + if (latitude > 90.0 || latitude < -90.0) { + return intents; + } + longitude = Double.parseDouble(matcher.group(2)); + if (longitude > 180.0 || longitude < -180.0) { + return intents; + } + } catch (NumberFormatException nfe) { + return intents; + } + final Conversation conversation = message.getConversation(); + String label; + if (conversation.getMode() == Conversation.MODE_SINGLE && message.getStatus() == Message.STATUS_RECEIVED) { + try { + label = "(" + URLEncoder.encode(message.getConversation().getName(), "UTF-8") + ")"; + } catch (UnsupportedEncodingException e) { + label = ""; + } + } else { + label = ""; + } - Intent locationPluginIntent = new Intent("de.pixart.messenger.location.show"); - locationPluginIntent.putExtra("latitude",latitude); - locationPluginIntent.putExtra("longitude",longitude); - if (message.getStatus() != Message.STATUS_RECEIVED) { - locationPluginIntent.putExtra("jid",conversation.getAccount().getJid().toString()); - locationPluginIntent.putExtra("name",conversation.getAccount().getJid().getLocalpart()); - } else { - Contact contact = message.getContact(); - if (contact != null) { - locationPluginIntent.putExtra("name", contact.getDisplayName()); - locationPluginIntent.putExtra("jid", contact.getJid().toString()); - } else { - locationPluginIntent.putExtra("name", UIHelper.getDisplayedMucCounterpart(message.getCounterpart())); - } - } - intents.add(locationPluginIntent); + Intent locationPluginIntent = new Intent("de.pixart.messenger.location.show"); + locationPluginIntent.putExtra("latitude", latitude); + locationPluginIntent.putExtra("longitude", longitude); + if (message.getStatus() != Message.STATUS_RECEIVED) { + locationPluginIntent.putExtra("jid", conversation.getAccount().getJid().toString()); + locationPluginIntent.putExtra("name", conversation.getAccount().getJid().getLocalpart()); + } else { + Contact contact = message.getContact(); + if (contact != null) { + locationPluginIntent.putExtra("name", contact.getDisplayName()); + locationPluginIntent.putExtra("jid", contact.getJid().toString()); + } else { + locationPluginIntent.putExtra("name", UIHelper.getDisplayedMucCounterpart(message.getCounterpart())); + } + } + intents.add(locationPluginIntent); - Intent geoIntent = new Intent(Intent.ACTION_VIEW); - geoIntent.setData(Uri.parse("geo:" + String.valueOf(latitude) + "," + String.valueOf(longitude) + "?q=" + String.valueOf(latitude) + "," + String.valueOf(longitude) + label)); - intents.add(geoIntent); + Intent geoIntent = new Intent(Intent.ACTION_VIEW); + geoIntent.setData(Uri.parse("geo:" + String.valueOf(latitude) + "," + String.valueOf(longitude) + "?q=" + String.valueOf(latitude) + "," + String.valueOf(longitude) + label)); + intents.add(geoIntent); - Intent httpIntent = new Intent(Intent.ACTION_VIEW); - httpIntent.setData(Uri.parse("https://maps.google.com/maps?q=loc:"+String.valueOf(latitude) + "," + String.valueOf(longitude) +label)); - intents.add(httpIntent); - return intents; - } + Intent httpIntent = new Intent(Intent.ACTION_VIEW); + httpIntent.setData(Uri.parse("https://maps.google.com/maps?q=loc:" + String.valueOf(latitude) + "," + String.valueOf(longitude) + label)); + intents.add(httpIntent); + return intents; + } } \ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/utils/MimeUtils.java b/src/main/java/de/pixart/messenger/utils/MimeUtils.java index f90822c2e..366ed308b 100644 --- a/src/main/java/de/pixart/messenger/utils/MimeUtils.java +++ b/src/main/java/de/pixart/messenger/utils/MimeUtils.java @@ -14,6 +14,7 @@ * limitations under the License. */ package de.pixart.messenger.utils; + import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -21,6 +22,7 @@ import java.io.InputStream; import java.util.HashMap; import java.util.Map; import java.util.Properties; + /** * Utilities for dealing with MIME types. * Used to implement java.net.URLConnection and android.webkit.MimeTypeMap. @@ -28,6 +30,7 @@ import java.util.Properties; public final class MimeUtils { private static final Map mimeTypeToExtensionMap = new HashMap(); private static final Map extensionToMimeTypeMap = new HashMap(); + static { // The following table is based on /etc/mime.types data minus // chemical/* MIME types and MIME types that don't map to any @@ -373,6 +376,7 @@ public final class MimeUtils { add("x-epoc/x-sisx-app", "sisx"); applyOverrides(); } + private static void add(String mimeType, String extension) { // If we have an existing x -> y mapping, we do not want to // override it with another mapping x -> y2. @@ -386,6 +390,7 @@ public final class MimeUtils { extensionToMimeTypeMap.put(extension, mimeType); } } + private static InputStream getContentTypesPropertiesStream() { // User override? String userTable = System.getProperty("content.types.user.table"); @@ -408,6 +413,7 @@ public final class MimeUtils { } return null; } + /** * This isn't what the RI does. The RI doesn't have hard-coded defaults, so supplying your * own "content.types.user.table" means you don't get any of the built-ins, and the built-ins @@ -436,10 +442,13 @@ public final class MimeUtils { } catch (IOException ignored) { } } + private MimeUtils() { } + /** * Returns true if the given MIME type has an entry in the map. + * * @param mimeType A MIME type (i.e. text/plain) * @return True iff there is a mimeType entry in the map. */ @@ -449,8 +458,10 @@ public final class MimeUtils { } return mimeTypeToExtensionMap.containsKey(mimeType); } + /** * Returns the MIME type for the given extension. + * * @param extension A file extension without the leading '.' * @return The MIME type for the given extension or null iff there is none. */ @@ -460,8 +471,10 @@ public final class MimeUtils { } return extensionToMimeTypeMap.get(extension.toLowerCase()); } + /** * Returns true if the given extension has a registered MIME type. + * * @param extension A file extension without the leading '.' * @return True iff there is an extension entry in the map. */ @@ -471,10 +484,12 @@ public final class MimeUtils { } return extensionToMimeTypeMap.containsKey(extension); } + /** * Returns the registered extension for the given MIME type. Note that some * MIME types map to multiple extensions. This call will return the most * common extension for the given MIME type. + * * @param mimeType A MIME type (i.e. text/plain) * @return The extension for the given MIME type or null iff there is none. */ diff --git a/src/main/java/de/pixart/messenger/utils/OnPhoneContactsLoadedListener.java b/src/main/java/de/pixart/messenger/utils/OnPhoneContactsLoadedListener.java index 049154745..f19258dda 100644 --- a/src/main/java/de/pixart/messenger/utils/OnPhoneContactsLoadedListener.java +++ b/src/main/java/de/pixart/messenger/utils/OnPhoneContactsLoadedListener.java @@ -5,5 +5,5 @@ import android.os.Bundle; import java.util.List; public interface OnPhoneContactsLoadedListener { - public void onPhoneContactsLoaded(List phoneContacts); + public void onPhoneContactsLoaded(List phoneContacts); } diff --git a/src/main/java/de/pixart/messenger/utils/PRNGFixes.java b/src/main/java/de/pixart/messenger/utils/PRNGFixes.java index be456a30c..211ebca8e 100644 --- a/src/main/java/de/pixart/messenger/utils/PRNGFixes.java +++ b/src/main/java/de/pixart/messenger/utils/PRNGFixes.java @@ -21,145 +21,144 @@ import java.security.Security; /** * Fixes for the output of the default PRNG having low entropy. - * + *

* The fixes need to be applied via {@link #apply()} before any use of Java * Cryptography Architecture primitives. A good place to invoke them is in the * application's {@code onCreate}. */ public final class PRNGFixes { - private static final int VERSION_CODE_JELLY_BEAN = 16; - private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18; - private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL = getBuildFingerprintAndDeviceSerial(); + private static final int VERSION_CODE_JELLY_BEAN = 16; + private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18; + private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL = getBuildFingerprintAndDeviceSerial(); - /** Hidden constructor to prevent instantiation. */ - private PRNGFixes() { - } + /** + * Hidden constructor to prevent instantiation. + */ + private PRNGFixes() { + } - /** - * Applies all fixes. - * - * @throws SecurityException - * if a fix is needed but could not be applied. - */ - public static void apply() { - applyOpenSSLFix(); - installLinuxPRNGSecureRandom(); - } + /** + * Applies all fixes. + * + * @throws SecurityException if a fix is needed but could not be applied. + */ + public static void apply() { + applyOpenSSLFix(); + installLinuxPRNGSecureRandom(); + } - /** - * Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the - * fix is not needed. - * - * @throws SecurityException - * if the fix is needed but could not be applied. - */ - private static void applyOpenSSLFix() throws SecurityException { - if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN) - || (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) { - // No need to apply the fix - return; - } + /** + * Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the + * fix is not needed. + * + * @throws SecurityException if the fix is needed but could not be applied. + */ + private static void applyOpenSSLFix() throws SecurityException { + if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN) + || (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) { + // No need to apply the fix + return; + } - try { - // Mix in the device- and invocation-specific seed. - Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto") - .getMethod("RAND_seed", byte[].class) - .invoke(null, generateSeed()); + try { + // Mix in the device- and invocation-specific seed. + Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto") + .getMethod("RAND_seed", byte[].class) + .invoke(null, generateSeed()); - // Mix output of Linux PRNG into OpenSSL's PRNG - int bytesRead = (Integer) Class - .forName( - "org.apache.harmony.xnet.provider.jsse.NativeCrypto") - .getMethod("RAND_load_file", String.class, long.class) - .invoke(null, "/dev/urandom", 1024); - if (bytesRead != 1024) { - throw new IOException( - "Unexpected number of bytes read from Linux PRNG: " - + bytesRead); - } - } catch (Exception e) { - throw new SecurityException("Failed to seed OpenSSL PRNG", e); - } - } + // Mix output of Linux PRNG into OpenSSL's PRNG + int bytesRead = (Integer) Class + .forName( + "org.apache.harmony.xnet.provider.jsse.NativeCrypto") + .getMethod("RAND_load_file", String.class, long.class) + .invoke(null, "/dev/urandom", 1024); + if (bytesRead != 1024) { + throw new IOException( + "Unexpected number of bytes read from Linux PRNG: " + + bytesRead); + } + } catch (Exception e) { + throw new SecurityException("Failed to seed OpenSSL PRNG", e); + } + } - /** - * Installs a Linux PRNG-backed {@code SecureRandom} implementation as the - * default. Does nothing if the implementation is already the default or if - * there is not need to install the implementation. - * - * @throws SecurityException - * if the fix is needed but could not be applied. - */ - private static void installLinuxPRNGSecureRandom() throws SecurityException { - if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) { - // No need to apply the fix - return; - } + /** + * Installs a Linux PRNG-backed {@code SecureRandom} implementation as the + * default. Does nothing if the implementation is already the default or if + * there is not need to install the implementation. + * + * @throws SecurityException if the fix is needed but could not be applied. + */ + private static void installLinuxPRNGSecureRandom() throws SecurityException { + if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) { + // No need to apply the fix + return; + } - // Install a Linux PRNG-based SecureRandom implementation as the - // default, if not yet installed. - Provider[] secureRandomProviders = Security - .getProviders("SecureRandom.SHA1PRNG"); - if ((secureRandomProviders == null) - || (secureRandomProviders.length < 1) - || (!LinuxPRNGSecureRandomProvider.class - .equals(secureRandomProviders[0].getClass()))) { - Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1); - } + // Install a Linux PRNG-based SecureRandom implementation as the + // default, if not yet installed. + Provider[] secureRandomProviders = Security + .getProviders("SecureRandom.SHA1PRNG"); + if ((secureRandomProviders == null) + || (secureRandomProviders.length < 1) + || (!LinuxPRNGSecureRandomProvider.class + .equals(secureRandomProviders[0].getClass()))) { + Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1); + } - // Assert that new SecureRandom() and - // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed - // by the Linux PRNG-based SecureRandom implementation. - SecureRandom rng1 = new SecureRandom(); - if (!LinuxPRNGSecureRandomProvider.class.equals(rng1.getProvider() - .getClass())) { - throw new SecurityException( - "new SecureRandom() backed by wrong Provider: " - + rng1.getProvider().getClass()); - } + // Assert that new SecureRandom() and + // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed + // by the Linux PRNG-based SecureRandom implementation. + SecureRandom rng1 = new SecureRandom(); + if (!LinuxPRNGSecureRandomProvider.class.equals(rng1.getProvider() + .getClass())) { + throw new SecurityException( + "new SecureRandom() backed by wrong Provider: " + + rng1.getProvider().getClass()); + } - SecureRandom rng2; - try { - rng2 = SecureRandom.getInstance("SHA1PRNG"); - } catch (NoSuchAlgorithmException e) { - throw new SecurityException("SHA1PRNG not available", e); - } - if (!LinuxPRNGSecureRandomProvider.class.equals(rng2.getProvider() - .getClass())) { - throw new SecurityException( - "SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" - + " Provider: " + rng2.getProvider().getClass()); - } - } + SecureRandom rng2; + try { + rng2 = SecureRandom.getInstance("SHA1PRNG"); + } catch (NoSuchAlgorithmException e) { + throw new SecurityException("SHA1PRNG not available", e); + } + if (!LinuxPRNGSecureRandomProvider.class.equals(rng2.getProvider() + .getClass())) { + throw new SecurityException( + "SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" + + " Provider: " + rng2.getProvider().getClass()); + } + } - /** - * {@code Provider} of {@code SecureRandom} engines which pass through all - * requests to the Linux PRNG. - */ - private static class LinuxPRNGSecureRandomProvider extends Provider { + /** + * {@code Provider} of {@code SecureRandom} engines which pass through all + * requests to the Linux PRNG. + */ + private static class LinuxPRNGSecureRandomProvider extends Provider { - public LinuxPRNGSecureRandomProvider() { - super("LinuxPRNG", 1.0, - "A Linux-specific random number provider that uses" - + " /dev/urandom"); - // Although /dev/urandom is not a SHA-1 PRNG, some apps - // explicitly request a SHA1PRNG SecureRandom and we thus need to - // prevent them from getting the default implementation whose output - // may have low entropy. - put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName()); - put("SecureRandom.SHA1PRNG ImplementedIn", "Software"); - } - } + public LinuxPRNGSecureRandomProvider() { + super("LinuxPRNG", 1.0, + "A Linux-specific random number provider that uses" + + " /dev/urandom"); + // Although /dev/urandom is not a SHA-1 PRNG, some apps + // explicitly request a SHA1PRNG SecureRandom and we thus need to + // prevent them from getting the default implementation whose output + // may have low entropy. + put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName()); + put("SecureRandom.SHA1PRNG ImplementedIn", "Software"); + } + } - /** - * {@link SecureRandomSpi} which passes all requests to the Linux PRNG ( - * {@code /dev/urandom}). - */ - public static class LinuxPRNGSecureRandom extends SecureRandomSpi { + /** + * {@link SecureRandomSpi} which passes all requests to the Linux PRNG ( + * {@code /dev/urandom}). + */ + public static class LinuxPRNGSecureRandom extends SecureRandomSpi { /* - * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed + * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed * are passed through to the Linux PRNG (/dev/urandom). Instances of * this class seed themselves by mixing in the current time, PID, UID, * build fingerprint, and hardware serial number (where available) into @@ -170,158 +169,158 @@ public final class PRNGFixes { * duplicated PRNG output. */ - private static final File URANDOM_FILE = new File("/dev/urandom"); + private static final File URANDOM_FILE = new File("/dev/urandom"); - private static final Object sLock = new Object(); + private static final Object sLock = new Object(); - /** - * Input stream for reading from Linux PRNG or {@code null} if not yet - * opened. - * - * @GuardedBy("sLock") - */ - private static DataInputStream sUrandomIn; + /** + * Input stream for reading from Linux PRNG or {@code null} if not yet + * opened. + * + * @GuardedBy("sLock") + */ + private static DataInputStream sUrandomIn; - /** - * Output stream for writing to Linux PRNG or {@code null} if not yet - * opened. - * - * @GuardedBy("sLock") - */ - private static OutputStream sUrandomOut; + /** + * Output stream for writing to Linux PRNG or {@code null} if not yet + * opened. + * + * @GuardedBy("sLock") + */ + private static OutputStream sUrandomOut; - /** - * Whether this engine instance has been seeded. This is needed because - * each instance needs to seed itself if the client does not explicitly - * seed it. - */ - private boolean mSeeded; + /** + * Whether this engine instance has been seeded. This is needed because + * each instance needs to seed itself if the client does not explicitly + * seed it. + */ + private boolean mSeeded; - @Override - protected void engineSetSeed(byte[] bytes) { - try { - OutputStream out; - synchronized (sLock) { - out = getUrandomOutputStream(); - } - out.write(bytes); - out.flush(); - } catch (IOException e) { - // On a small fraction of devices /dev/urandom is not writable. - // Log and ignore. - Log.w(PRNGFixes.class.getSimpleName(), - "Failed to mix seed into " + URANDOM_FILE); - } finally { - mSeeded = true; - } - } + @Override + protected void engineSetSeed(byte[] bytes) { + try { + OutputStream out; + synchronized (sLock) { + out = getUrandomOutputStream(); + } + out.write(bytes); + out.flush(); + } catch (IOException e) { + // On a small fraction of devices /dev/urandom is not writable. + // Log and ignore. + Log.w(PRNGFixes.class.getSimpleName(), + "Failed to mix seed into " + URANDOM_FILE); + } finally { + mSeeded = true; + } + } - @Override - protected void engineNextBytes(byte[] bytes) { - if (!mSeeded) { - // Mix in the device- and invocation-specific seed. - engineSetSeed(generateSeed()); - } + @Override + protected void engineNextBytes(byte[] bytes) { + if (!mSeeded) { + // Mix in the device- and invocation-specific seed. + engineSetSeed(generateSeed()); + } - try { - DataInputStream in; - synchronized (sLock) { - in = getUrandomInputStream(); - } - synchronized (in) { - in.readFully(bytes); - } - } catch (IOException e) { - throw new SecurityException("Failed to read from " - + URANDOM_FILE, e); - } - } + try { + DataInputStream in; + synchronized (sLock) { + in = getUrandomInputStream(); + } + synchronized (in) { + in.readFully(bytes); + } + } catch (IOException e) { + throw new SecurityException("Failed to read from " + + URANDOM_FILE, e); + } + } - @Override - protected byte[] engineGenerateSeed(int size) { - byte[] seed = new byte[size]; - engineNextBytes(seed); - return seed; - } + @Override + protected byte[] engineGenerateSeed(int size) { + byte[] seed = new byte[size]; + engineNextBytes(seed); + return seed; + } - private DataInputStream getUrandomInputStream() { - synchronized (sLock) { - if (sUrandomIn == null) { - // NOTE: Consider inserting a BufferedInputStream between - // DataInputStream and FileInputStream if you need higher - // PRNG output performance and can live with future PRNG - // output being pulled into this process prematurely. - try { - sUrandomIn = new DataInputStream(new FileInputStream( - URANDOM_FILE)); - } catch (IOException e) { - throw new SecurityException("Failed to open " - + URANDOM_FILE + " for reading", e); - } - } - return sUrandomIn; - } - } + private DataInputStream getUrandomInputStream() { + synchronized (sLock) { + if (sUrandomIn == null) { + // NOTE: Consider inserting a BufferedInputStream between + // DataInputStream and FileInputStream if you need higher + // PRNG output performance and can live with future PRNG + // output being pulled into this process prematurely. + try { + sUrandomIn = new DataInputStream(new FileInputStream( + URANDOM_FILE)); + } catch (IOException e) { + throw new SecurityException("Failed to open " + + URANDOM_FILE + " for reading", e); + } + } + return sUrandomIn; + } + } - private OutputStream getUrandomOutputStream() throws IOException { - synchronized (sLock) { - if (sUrandomOut == null) { - sUrandomOut = new FileOutputStream(URANDOM_FILE); - } - return sUrandomOut; - } - } - } + private OutputStream getUrandomOutputStream() throws IOException { + synchronized (sLock) { + if (sUrandomOut == null) { + sUrandomOut = new FileOutputStream(URANDOM_FILE); + } + return sUrandomOut; + } + } + } - /** - * Generates a device- and invocation-specific seed to be mixed into the - * Linux PRNG. - */ - private static byte[] generateSeed() { - try { - ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream(); - DataOutputStream seedBufferOut = new DataOutputStream(seedBuffer); - seedBufferOut.writeLong(System.currentTimeMillis()); - seedBufferOut.writeLong(System.nanoTime()); - seedBufferOut.writeInt(Process.myPid()); - seedBufferOut.writeInt(Process.myUid()); - seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL); - seedBufferOut.close(); - return seedBuffer.toByteArray(); - } catch (IOException e) { - throw new SecurityException("Failed to generate seed", e); - } - } + /** + * Generates a device- and invocation-specific seed to be mixed into the + * Linux PRNG. + */ + private static byte[] generateSeed() { + try { + ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream(); + DataOutputStream seedBufferOut = new DataOutputStream(seedBuffer); + seedBufferOut.writeLong(System.currentTimeMillis()); + seedBufferOut.writeLong(System.nanoTime()); + seedBufferOut.writeInt(Process.myPid()); + seedBufferOut.writeInt(Process.myUid()); + seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL); + seedBufferOut.close(); + return seedBuffer.toByteArray(); + } catch (IOException e) { + throw new SecurityException("Failed to generate seed", e); + } + } - /** - * Gets the hardware serial number of this device. - * - * @return serial number or {@code null} if not available. - */ - private static String getDeviceSerialNumber() { - // We're using the Reflection API because Build.SERIAL is only available - // since API Level 9 (Gingerbread, Android 2.3). - try { - return (String) Build.class.getField("SERIAL").get(null); - } catch (Exception ignored) { - return null; - } - } + /** + * Gets the hardware serial number of this device. + * + * @return serial number or {@code null} if not available. + */ + private static String getDeviceSerialNumber() { + // We're using the Reflection API because Build.SERIAL is only available + // since API Level 9 (Gingerbread, Android 2.3). + try { + return (String) Build.class.getField("SERIAL").get(null); + } catch (Exception ignored) { + return null; + } + } - private static byte[] getBuildFingerprintAndDeviceSerial() { - StringBuilder result = new StringBuilder(); - String fingerprint = Build.FINGERPRINT; - if (fingerprint != null) { - result.append(fingerprint); - } - String serial = getDeviceSerialNumber(); - if (serial != null) { - result.append(serial); - } - try { - return result.toString().getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("UTF-8 encoding not supported"); - } - } + private static byte[] getBuildFingerprintAndDeviceSerial() { + StringBuilder result = new StringBuilder(); + String fingerprint = Build.FINGERPRINT; + if (fingerprint != null) { + result.append(fingerprint); + } + String serial = getDeviceSerialNumber(); + if (serial != null) { + result.append(serial); + } + try { + return result.toString().getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("UTF-8 encoding not supported"); + } + } } \ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/utils/PhoneHelper.java b/src/main/java/de/pixart/messenger/utils/PhoneHelper.java index 805b76673..cdc51f729 100644 --- a/src/main/java/de/pixart/messenger/utils/PhoneHelper.java +++ b/src/main/java/de/pixart/messenger/utils/PhoneHelper.java @@ -19,124 +19,124 @@ import java.util.concurrent.RejectedExecutionException; public class PhoneHelper { - public static void loadPhoneContacts(Context context, final OnPhoneContactsLoadedListener listener) { - final List phoneContacts = new ArrayList<>(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M - && context.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { - listener.onPhoneContactsLoaded(phoneContacts); - return; - } - final String[] PROJECTION = new String[]{ContactsContract.Data._ID, - ContactsContract.Data.DISPLAY_NAME, - ContactsContract.Data.PHOTO_URI, - ContactsContract.Data.LOOKUP_KEY, - ContactsContract.CommonDataKinds.Im.DATA}; - - final String SELECTION = "(" + ContactsContract.Data.MIMETYPE + "=\"" - + ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE - + "\") AND (" + ContactsContract.CommonDataKinds.Im.PROTOCOL - + "=\"" + ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER - + "\")"; - - CursorLoader mCursorLoader = new NotThrowCursorLoader(context, - ContactsContract.Data.CONTENT_URI, PROJECTION, SELECTION, null, - null); - mCursorLoader.registerListener(0, new OnLoadCompleteListener() { - - @Override - public void onLoadComplete(Loader arg0, Cursor cursor) { - if (cursor != null) { - while (cursor.moveToNext()) { - Bundle contact = new Bundle(); - contact.putInt("phoneid", cursor.getInt(cursor - .getColumnIndex(ContactsContract.Data._ID))); - contact.putString( - "displayname", - cursor.getString(cursor - .getColumnIndex(ContactsContract.Data.DISPLAY_NAME))); - contact.putString("photouri", cursor.getString(cursor - .getColumnIndex(ContactsContract.Data.PHOTO_URI))); - contact.putString("lookup", cursor.getString(cursor - .getColumnIndex(ContactsContract.Data.LOOKUP_KEY))); - - contact.putString( - "jid", - cursor.getString(cursor - .getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA))); - phoneContacts.add(contact); - } - cursor.close(); - } - - if (listener != null) { - listener.onPhoneContactsLoaded(phoneContacts); - } - } - }); - try { - mCursorLoader.startLoading(); - } catch (RejectedExecutionException e) { - if (listener != null) { - listener.onPhoneContactsLoaded(phoneContacts); - } - } - } - - private static class NotThrowCursorLoader extends CursorLoader { - - public NotThrowCursorLoader(Context c, Uri u, String[] p, String s, String[] sa, String so) { - super(c, u, p, s, sa, so); - } - - @Override - public Cursor loadInBackground() { - - try { - return (super.loadInBackground()); - } catch (Throwable e) { - return(null); - } - } - - } - - public static Uri getSelfiUri(Context context) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M - && context.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { - return null; - } - String[] mProjection = new String[]{Profile._ID, Profile.PHOTO_URI}; - Cursor mProfileCursor = context.getContentResolver().query( - Profile.CONTENT_URI, mProjection, null, null, null); - - if (mProfileCursor == null || mProfileCursor.getCount() == 0) { - return null; - } else { - mProfileCursor.moveToFirst(); - String uri = mProfileCursor.getString(1); - mProfileCursor.close(); - if (uri == null) { - return null; - } else { - return Uri.parse(uri); - } - } - } - - public static String getVersionName(Context context) { - final String packageName = context == null ? null : context.getPackageName(); - if (packageName != null) { - try { - return context.getPackageManager().getPackageInfo(packageName, 0).versionName; - } catch (final PackageManager.NameNotFoundException | RuntimeException e) { - return "unknown"; - } - } else { - return "unknown"; - } - } - - public static String getOSVersion(Context context) { - return "Android/" + android.os.Build.MODEL + "/" + android.os.Build.VERSION.RELEASE; + public static void loadPhoneContacts(Context context, final OnPhoneContactsLoadedListener listener) { + final List phoneContacts = new ArrayList<>(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + && context.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { + listener.onPhoneContactsLoaded(phoneContacts); + return; + } + final String[] PROJECTION = new String[]{ContactsContract.Data._ID, + ContactsContract.Data.DISPLAY_NAME, + ContactsContract.Data.PHOTO_URI, + ContactsContract.Data.LOOKUP_KEY, + ContactsContract.CommonDataKinds.Im.DATA}; + + final String SELECTION = "(" + ContactsContract.Data.MIMETYPE + "=\"" + + ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE + + "\") AND (" + ContactsContract.CommonDataKinds.Im.PROTOCOL + + "=\"" + ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER + + "\")"; + + CursorLoader mCursorLoader = new NotThrowCursorLoader(context, + ContactsContract.Data.CONTENT_URI, PROJECTION, SELECTION, null, + null); + mCursorLoader.registerListener(0, new OnLoadCompleteListener() { + + @Override + public void onLoadComplete(Loader arg0, Cursor cursor) { + if (cursor != null) { + while (cursor.moveToNext()) { + Bundle contact = new Bundle(); + contact.putInt("phoneid", cursor.getInt(cursor + .getColumnIndex(ContactsContract.Data._ID))); + contact.putString( + "displayname", + cursor.getString(cursor + .getColumnIndex(ContactsContract.Data.DISPLAY_NAME))); + contact.putString("photouri", cursor.getString(cursor + .getColumnIndex(ContactsContract.Data.PHOTO_URI))); + contact.putString("lookup", cursor.getString(cursor + .getColumnIndex(ContactsContract.Data.LOOKUP_KEY))); + + contact.putString( + "jid", + cursor.getString(cursor + .getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA))); + phoneContacts.add(contact); + } + cursor.close(); + } + + if (listener != null) { + listener.onPhoneContactsLoaded(phoneContacts); + } + } + }); + try { + mCursorLoader.startLoading(); + } catch (RejectedExecutionException e) { + if (listener != null) { + listener.onPhoneContactsLoaded(phoneContacts); + } + } + } + + private static class NotThrowCursorLoader extends CursorLoader { + + public NotThrowCursorLoader(Context c, Uri u, String[] p, String s, String[] sa, String so) { + super(c, u, p, s, sa, so); + } + + @Override + public Cursor loadInBackground() { + + try { + return (super.loadInBackground()); + } catch (Throwable e) { + return (null); + } + } + + } + + public static Uri getSelfiUri(Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + && context.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { + return null; + } + String[] mProjection = new String[]{Profile._ID, Profile.PHOTO_URI}; + Cursor mProfileCursor = context.getContentResolver().query( + Profile.CONTENT_URI, mProjection, null, null, null); + + if (mProfileCursor == null || mProfileCursor.getCount() == 0) { + return null; + } else { + mProfileCursor.moveToFirst(); + String uri = mProfileCursor.getString(1); + mProfileCursor.close(); + if (uri == null) { + return null; + } else { + return Uri.parse(uri); + } + } + } + + public static String getVersionName(Context context) { + final String packageName = context == null ? null : context.getPackageName(); + if (packageName != null) { + try { + return context.getPackageManager().getPackageInfo(packageName, 0).versionName; + } catch (final PackageManager.NameNotFoundException | RuntimeException e) { + return "unknown"; + } + } else { + return "unknown"; + } + } + + public static String getOSVersion(Context context) { + return "Android/" + android.os.Build.MODEL + "/" + android.os.Build.VERSION.RELEASE; } } diff --git a/src/main/java/de/pixart/messenger/utils/SSLSocketHelper.java b/src/main/java/de/pixart/messenger/utils/SSLSocketHelper.java index 661fda807..ad3629354 100644 --- a/src/main/java/de/pixart/messenger/utils/SSLSocketHelper.java +++ b/src/main/java/de/pixart/messenger/utils/SSLSocketHelper.java @@ -14,60 +14,60 @@ import javax.net.ssl.SSLSocketFactory; public class SSLSocketHelper { - public static void setSecurity(final SSLSocket sslSocket) throws NoSuchAlgorithmException { - final String[] supportProtocols; - final Collection supportedProtocols = new LinkedList<>( - Arrays.asList(sslSocket.getSupportedProtocols())); - supportedProtocols.remove("SSLv3"); - supportProtocols = supportedProtocols.toArray(new String[supportedProtocols.size()]); + public static void setSecurity(final SSLSocket sslSocket) throws NoSuchAlgorithmException { + final String[] supportProtocols; + final Collection supportedProtocols = new LinkedList<>( + Arrays.asList(sslSocket.getSupportedProtocols())); + supportedProtocols.remove("SSLv3"); + supportProtocols = supportedProtocols.toArray(new String[supportedProtocols.size()]); - sslSocket.setEnabledProtocols(supportProtocols); + sslSocket.setEnabledProtocols(supportProtocols); - final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites( - sslSocket.getSupportedCipherSuites()); - if (cipherSuites.length > 0) { - sslSocket.setEnabledCipherSuites(cipherSuites); - } - } + final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites( + sslSocket.getSupportedCipherSuites()); + if (cipherSuites.length > 0) { + sslSocket.setEnabledCipherSuites(cipherSuites); + } + } - public static void setSNIHost(final SSLSocketFactory factory, final SSLSocket socket, final String hostname) { - if (factory instanceof android.net.SSLCertificateSocketFactory && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { - ((android.net.SSLCertificateSocketFactory) factory).setHostname(socket, hostname); - } else { - try { - socket.getClass().getMethod("setHostname", String.class).invoke(socket, hostname); - } catch (Throwable e) { - // ignore any error, we just can't set the hostname... - } - } - } + public static void setSNIHost(final SSLSocketFactory factory, final SSLSocket socket, final String hostname) { + if (factory instanceof android.net.SSLCertificateSocketFactory && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { + ((android.net.SSLCertificateSocketFactory) factory).setHostname(socket, hostname); + } else { + try { + socket.getClass().getMethod("setHostname", String.class).invoke(socket, hostname); + } catch (Throwable e) { + // ignore any error, we just can't set the hostname... + } + } + } - public static void setAlpnProtocol(final SSLSocketFactory factory, final SSLSocket socket, final String protocol) { - try { - if (factory instanceof android.net.SSLCertificateSocketFactory && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { - // can't call directly because of @hide? - //((android.net.SSLCertificateSocketFactory)factory).setAlpnProtocols(new byte[][]{protocol.getBytes("UTF-8")}); - android.net.SSLCertificateSocketFactory.class.getMethod("setAlpnProtocols", byte[][].class).invoke(socket, new Object[]{new byte[][]{protocol.getBytes("UTF-8")}}); - } else { - final Method method = socket.getClass().getMethod("setAlpnProtocols", byte[].class); - // the concatenation of 8-bit, length prefixed protocol names, just one in our case... - // http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04#page-4 - final byte[] protocolUTF8Bytes = protocol.getBytes("UTF-8"); - final byte[] lengthPrefixedProtocols = new byte[protocolUTF8Bytes.length + 1]; - lengthPrefixedProtocols[0] = (byte) protocol.length(); // cannot be over 255 anyhow - System.arraycopy(protocolUTF8Bytes, 0, lengthPrefixedProtocols, 1, protocolUTF8Bytes.length); - method.invoke(socket, new Object[]{lengthPrefixedProtocols}); - } - } catch (Throwable e) { - // ignore any error, we just can't set the alpn protocol... - } - } + public static void setAlpnProtocol(final SSLSocketFactory factory, final SSLSocket socket, final String protocol) { + try { + if (factory instanceof android.net.SSLCertificateSocketFactory && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { + // can't call directly because of @hide? + //((android.net.SSLCertificateSocketFactory)factory).setAlpnProtocols(new byte[][]{protocol.getBytes("UTF-8")}); + android.net.SSLCertificateSocketFactory.class.getMethod("setAlpnProtocols", byte[][].class).invoke(socket, new Object[]{new byte[][]{protocol.getBytes("UTF-8")}}); + } else { + final Method method = socket.getClass().getMethod("setAlpnProtocols", byte[].class); + // the concatenation of 8-bit, length prefixed protocol names, just one in our case... + // http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04#page-4 + final byte[] protocolUTF8Bytes = protocol.getBytes("UTF-8"); + final byte[] lengthPrefixedProtocols = new byte[protocolUTF8Bytes.length + 1]; + lengthPrefixedProtocols[0] = (byte) protocol.length(); // cannot be over 255 anyhow + System.arraycopy(protocolUTF8Bytes, 0, lengthPrefixedProtocols, 1, protocolUTF8Bytes.length); + method.invoke(socket, new Object[]{lengthPrefixedProtocols}); + } + } catch (Throwable e) { + // ignore any error, we just can't set the alpn protocol... + } + } - public static SSLContext getSSLContext() throws NoSuchAlgorithmException { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - return SSLContext.getInstance("TLSv1.2"); - } else { - return SSLContext.getInstance("TLS"); - } - } + public static SSLContext getSSLContext() throws NoSuchAlgorithmException { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + return SSLContext.getInstance("TLSv1.2"); + } else { + return SSLContext.getInstance("TLS"); + } + } } diff --git a/src/main/java/de/pixart/messenger/utils/SerialSingleThreadExecutor.java b/src/main/java/de/pixart/messenger/utils/SerialSingleThreadExecutor.java index 116149ec1..6b60a424f 100644 --- a/src/main/java/de/pixart/messenger/utils/SerialSingleThreadExecutor.java +++ b/src/main/java/de/pixart/messenger/utils/SerialSingleThreadExecutor.java @@ -9,43 +9,43 @@ import java.util.concurrent.Executors; public class SerialSingleThreadExecutor implements Executor { - final Executor executor = Executors.newSingleThreadExecutor(); - protected final Queue tasks = new ArrayDeque(); - Runnable active; - - public SerialSingleThreadExecutor() { - this(false); - } - - public SerialSingleThreadExecutor(boolean prepareLooper) { - if (prepareLooper) { - execute(new Runnable() { - @Override - public void run() { - Looper.prepare(); - } - }); - } - } - - public synchronized void execute(final Runnable r) { - tasks.offer(new Runnable() { - public void run() { - try { - r.run(); - } finally { - scheduleNext(); - } - } - }); - if (active == null) { - scheduleNext(); - } - } - - protected synchronized void scheduleNext() { - if ((active = tasks.poll()) != null) { - executor.execute(active); - } - } + final Executor executor = Executors.newSingleThreadExecutor(); + protected final Queue tasks = new ArrayDeque(); + Runnable active; + + public SerialSingleThreadExecutor() { + this(false); + } + + public SerialSingleThreadExecutor(boolean prepareLooper) { + if (prepareLooper) { + execute(new Runnable() { + @Override + public void run() { + Looper.prepare(); + } + }); + } + } + + public synchronized void execute(final Runnable r) { + tasks.offer(new Runnable() { + public void run() { + try { + r.run(); + } finally { + scheduleNext(); + } + } + }); + if (active == null) { + scheduleNext(); + } + } + + protected synchronized void scheduleNext() { + if ((active = tasks.poll()) != null) { + executor.execute(active); + } + } } \ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/utils/SocksSocketFactory.java b/src/main/java/de/pixart/messenger/utils/SocksSocketFactory.java index ee7be8e9c..e3465fb9b 100644 --- a/src/main/java/de/pixart/messenger/utils/SocksSocketFactory.java +++ b/src/main/java/de/pixart/messenger/utils/SocksSocketFactory.java @@ -12,46 +12,46 @@ import de.pixart.messenger.Config; public class SocksSocketFactory { - public static void createSocksConnection(Socket socket, String destination, int port) throws IOException { - InputStream proxyIs = socket.getInputStream(); - OutputStream proxyOs = socket.getOutputStream(); - proxyOs.write(new byte[]{0x05, 0x01, 0x00}); - byte[] response = new byte[2]; - proxyIs.read(response); - byte[] dest = destination.getBytes(); - ByteBuffer request = ByteBuffer.allocate(7 + dest.length); - request.put(new byte[]{0x05, 0x01, 0x00, 0x03}); - request.put((byte) dest.length); - request.put(dest); - request.putShort((short) port); - proxyOs.write(request.array()); - response = new byte[7 + dest.length]; - proxyIs.read(response); - if (response[1] != 0x00) { - throw new SocksConnectionException(); - } - } - - public static Socket createSocket(InetSocketAddress address, String destination, int port) throws IOException { - Socket socket = new Socket(); - try { - socket.connect(address, Config.CONNECT_TIMEOUT * 1000); - } catch (IOException e) { - throw new SocksProxyNotFoundException(); - } - createSocksConnection(socket, destination, port); - return socket; - } - - public static Socket createSocketOverTor(String destination, int port) throws IOException { - return createSocket(new InetSocketAddress(InetAddress.getLocalHost(), 9050), destination, port); - } - - static class SocksConnectionException extends IOException { - - } - - public static class SocksProxyNotFoundException extends IOException { - - } + public static void createSocksConnection(Socket socket, String destination, int port) throws IOException { + InputStream proxyIs = socket.getInputStream(); + OutputStream proxyOs = socket.getOutputStream(); + proxyOs.write(new byte[]{0x05, 0x01, 0x00}); + byte[] response = new byte[2]; + proxyIs.read(response); + byte[] dest = destination.getBytes(); + ByteBuffer request = ByteBuffer.allocate(7 + dest.length); + request.put(new byte[]{0x05, 0x01, 0x00, 0x03}); + request.put((byte) dest.length); + request.put(dest); + request.putShort((short) port); + proxyOs.write(request.array()); + response = new byte[7 + dest.length]; + proxyIs.read(response); + if (response[1] != 0x00) { + throw new SocksConnectionException(); + } + } + + public static Socket createSocket(InetSocketAddress address, String destination, int port) throws IOException { + Socket socket = new Socket(); + try { + socket.connect(address, Config.CONNECT_TIMEOUT * 1000); + } catch (IOException e) { + throw new SocksProxyNotFoundException(); + } + createSocksConnection(socket, destination, port); + return socket; + } + + public static Socket createSocketOverTor(String destination, int port) throws IOException { + return createSocket(new InetSocketAddress(InetAddress.getLocalHost(), 9050), destination, port); + } + + static class SocksConnectionException extends IOException { + + } + + public static class SocksProxyNotFoundException extends IOException { + + } } diff --git a/src/main/java/de/pixart/messenger/utils/UIHelper.java b/src/main/java/de/pixart/messenger/utils/UIHelper.java index 100eff4ea..0bb87e6c7 100644 --- a/src/main/java/de/pixart/messenger/utils/UIHelper.java +++ b/src/main/java/de/pixart/messenger/utils/UIHelper.java @@ -24,310 +24,310 @@ import de.pixart.messenger.xmpp.jid.Jid; public class UIHelper { - private static String BLACK_HEART_SUIT = "\u2665"; - private static String HEAVY_BLACK_HEART_SUIT = "\u2764"; - private static String WHITE_HEART_SUIT = "\u2661"; + private static String BLACK_HEART_SUIT = "\u2665"; + private static String HEAVY_BLACK_HEART_SUIT = "\u2764"; + private static String WHITE_HEART_SUIT = "\u2661"; - public static final ArrayList HEARTS = new ArrayList<>(Arrays.asList(BLACK_HEART_SUIT,HEAVY_BLACK_HEART_SUIT,WHITE_HEART_SUIT)); + public static final ArrayList HEARTS = new ArrayList<>(Arrays.asList(BLACK_HEART_SUIT, HEAVY_BLACK_HEART_SUIT, WHITE_HEART_SUIT)); - private static final ArrayList LOCATION_QUESTIONS = new ArrayList<>(Arrays.asList( - "where are you", //en - "where are you now", //en - "where are you right now", //en - "whats your 20", //en - "what is your 20", //en - "what's your 20", //en - "whats your twenty", //en - "what is your twenty", //en - "what's your twenty", //en - "wo bist du", //de - "wo bist du jetzt", //de - "wo bist du gerade", //de - "wo seid ihr", //de - "wo seid ihr jetzt", //de - "wo seid ihr gerade", //de - "dónde estás", //es - "donde estas" //es - )); + private static final ArrayList LOCATION_QUESTIONS = new ArrayList<>(Arrays.asList( + "where are you", //en + "where are you now", //en + "where are you right now", //en + "whats your 20", //en + "what is your 20", //en + "what's your 20", //en + "whats your twenty", //en + "what is your twenty", //en + "what's your twenty", //en + "wo bist du", //de + "wo bist du jetzt", //de + "wo bist du gerade", //de + "wo seid ihr", //de + "wo seid ihr jetzt", //de + "wo seid ihr gerade", //de + "dónde estás", //es + "donde estas" //es + )); - private static final int SHORT_DATE_FLAGS = DateUtils.FORMAT_SHOW_DATE - | DateUtils.FORMAT_NO_YEAR | DateUtils.FORMAT_ABBREV_ALL; - private static final int FULL_DATE_FLAGS = DateUtils.FORMAT_SHOW_TIME - | DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE; + private static final int SHORT_DATE_FLAGS = DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_NO_YEAR | DateUtils.FORMAT_ABBREV_ALL; + private static final int FULL_DATE_FLAGS = DateUtils.FORMAT_SHOW_TIME + | DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE; - public static String readableTimeDifference(Context context, long time) { - return readableTimeDifference(context, time, false); - } + public static String readableTimeDifference(Context context, long time) { + return readableTimeDifference(context, time, false); + } - public static String readableTimeDifferenceFull(Context context, long time) { - return readableTimeDifference(context, time, true); - } + public static String readableTimeDifferenceFull(Context context, long time) { + return readableTimeDifference(context, time, true); + } - private static String readableTimeDifference(Context context, long time, - boolean fullDate) { - if (time == 0) { - return context.getString(R.string.just_now); - } - Date date = new Date(time); - long difference = (System.currentTimeMillis() - time) / 1000; - if (difference < 60) { - return context.getString(R.string.just_now); - } else if (difference < 60 * 2) { - return context.getString(R.string.minute_ago); - } else if (difference < 60 * 15) { - return context.getString(R.string.minutes_ago,Math.round(difference / 60.0)); - } else if (today(date)) { - java.text.DateFormat df = DateFormat.getTimeFormat(context); - return df.format(date); - } else { - if (fullDate) { - return DateUtils.formatDateTime(context, date.getTime(), - FULL_DATE_FLAGS); - } else { - return DateUtils.formatDateTime(context, date.getTime(), - SHORT_DATE_FLAGS); - } - } - } + private static String readableTimeDifference(Context context, long time, + boolean fullDate) { + if (time == 0) { + return context.getString(R.string.just_now); + } + Date date = new Date(time); + long difference = (System.currentTimeMillis() - time) / 1000; + if (difference < 60) { + return context.getString(R.string.just_now); + } else if (difference < 60 * 2) { + return context.getString(R.string.minute_ago); + } else if (difference < 60 * 15) { + return context.getString(R.string.minutes_ago, Math.round(difference / 60.0)); + } else if (today(date)) { + java.text.DateFormat df = DateFormat.getTimeFormat(context); + return df.format(date); + } else { + if (fullDate) { + return DateUtils.formatDateTime(context, date.getTime(), + FULL_DATE_FLAGS); + } else { + return DateUtils.formatDateTime(context, date.getTime(), + SHORT_DATE_FLAGS); + } + } + } - private static boolean today(Date date) { - return sameDay(date,new Date(System.currentTimeMillis())); - } + private static boolean today(Date date) { + return sameDay(date, new Date(System.currentTimeMillis())); + } - private static boolean sameDay(Date a, Date b) { - Calendar cal1 = Calendar.getInstance(); - Calendar cal2 = Calendar.getInstance(); - cal1.setTime(a); - cal2.setTime(b); - return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) - && cal1.get(Calendar.DAY_OF_YEAR) == cal2 - .get(Calendar.DAY_OF_YEAR); - } + private static boolean sameDay(Date a, Date b) { + Calendar cal1 = Calendar.getInstance(); + Calendar cal2 = Calendar.getInstance(); + cal1.setTime(a); + cal2.setTime(b); + return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) + && cal1.get(Calendar.DAY_OF_YEAR) == cal2 + .get(Calendar.DAY_OF_YEAR); + } - public static String lastseen(Context context, boolean active, long time) { - long difference = (System.currentTimeMillis() - time) / 1000; - active = active && difference <= 300; - if (active || difference < 60) { - return context.getString(R.string.last_seen_now); - } else if (difference < 60 * 2) { - return context.getString(R.string.last_seen_min); - } else if (difference < 60 * 60) { - return context.getString(R.string.last_seen_mins, - Math.round(difference / 60.0)); - } else if (difference < 60 * 60 * 2) { - return context.getString(R.string.last_seen_hour); - } else if (difference < 60 * 60 * 24) { - return context.getString(R.string.last_seen_hours, - Math.round(difference / (60.0 * 60.0))); - } else if (difference < 60 * 60 * 48) { - return context.getString(R.string.last_seen_day); - } else { - return context.getString(R.string.last_seen_days, - Math.round(difference / (60.0 * 60.0 * 24.0))); - } - } + public static String lastseen(Context context, boolean active, long time) { + long difference = (System.currentTimeMillis() - time) / 1000; + active = active && difference <= 300; + if (active || difference < 60) { + return context.getString(R.string.last_seen_now); + } else if (difference < 60 * 2) { + return context.getString(R.string.last_seen_min); + } else if (difference < 60 * 60) { + return context.getString(R.string.last_seen_mins, + Math.round(difference / 60.0)); + } else if (difference < 60 * 60 * 2) { + return context.getString(R.string.last_seen_hour); + } else if (difference < 60 * 60 * 24) { + return context.getString(R.string.last_seen_hours, + Math.round(difference / (60.0 * 60.0))); + } else if (difference < 60 * 60 * 48) { + return context.getString(R.string.last_seen_day); + } else { + return context.getString(R.string.last_seen_days, + Math.round(difference / (60.0 * 60.0 * 24.0))); + } + } - public static int getColorForName(String name) { - if (name == null || name.isEmpty()) { - return 0xFF202020; - } - int colors[] = {0xFFe91e63, 0xFF9c27b0, 0xFF673ab7, 0xFF3f51b5, - 0xFF5677fc, 0xFF03a9f4, 0xFF00bcd4, 0xFF009688, 0xFFff5722, - 0xFF795548, 0xFF607d8b}; - return colors[(int) ((name.hashCode() & 0xffffffffl) % colors.length)]; - } + public static int getColorForName(String name) { + if (name == null || name.isEmpty()) { + return 0xFF202020; + } + int colors[] = {0xFFe91e63, 0xFF9c27b0, 0xFF673ab7, 0xFF3f51b5, + 0xFF5677fc, 0xFF03a9f4, 0xFF00bcd4, 0xFF009688, 0xFFff5722, + 0xFF795548, 0xFF607d8b}; + return colors[(int) ((name.hashCode() & 0xffffffffl) % colors.length)]; + } - public static Pair getMessagePreview(final Context context, final Message message) { - final Transferable d = message.getTransferable(); - if (d != null ) { - switch (d.getStatus()) { - case Transferable.STATUS_CHECKING: - return new Pair<>(context.getString(R.string.checking_x, - getFileDescriptionString(context,message)),true); - case Transferable.STATUS_DOWNLOADING: - return new Pair<>(context.getString(R.string.receiving_x_file, - getFileDescriptionString(context,message), - d.getProgress()),true); - case Transferable.STATUS_OFFER: - case Transferable.STATUS_OFFER_CHECK_FILESIZE: - return new Pair<>(context.getString(R.string.x_file_offered_for_download, - getFileDescriptionString(context,message)),true); - case Transferable.STATUS_DELETED: - return new Pair<>(context.getString(R.string.file_deleted),true); - case Transferable.STATUS_FAILED: - return new Pair<>(context.getString(R.string.file_transmission_failed),true); - case Transferable.STATUS_UPLOADING: - if (message.getStatus() == Message.STATUS_OFFERED) { - return new Pair<>(context.getString(R.string.offering_x_file, - getFileDescriptionString(context, message)), true); - } else { - return new Pair<>(context.getString(R.string.sending_x_file, - getFileDescriptionString(context, message)), true); - } - default: - return new Pair<>("",false); - } - } else if (message.getEncryption() == Message.ENCRYPTION_PGP) { - return new Pair<>(context.getString(R.string.pgp_message),true); - } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { - return new Pair<>(context.getString(R.string.decryption_failed), true); - } else if (message.getType() == Message.TYPE_FILE || message.getType() == Message.TYPE_IMAGE) { - if (message.getStatus() == Message.STATUS_RECEIVED) { - return new Pair<>(context.getString(R.string.received_x_file, - getFileDescriptionString(context, message)), true); - } else { - return new Pair<>(getFileDescriptionString(context,message),true); - } - } else { - String body = message.getBody(); + public static Pair getMessagePreview(final Context context, final Message message) { + final Transferable d = message.getTransferable(); + if (d != null) { + switch (d.getStatus()) { + case Transferable.STATUS_CHECKING: + return new Pair<>(context.getString(R.string.checking_x, + getFileDescriptionString(context, message)), true); + case Transferable.STATUS_DOWNLOADING: + return new Pair<>(context.getString(R.string.receiving_x_file, + getFileDescriptionString(context, message), + d.getProgress()), true); + case Transferable.STATUS_OFFER: + case Transferable.STATUS_OFFER_CHECK_FILESIZE: + return new Pair<>(context.getString(R.string.x_file_offered_for_download, + getFileDescriptionString(context, message)), true); + case Transferable.STATUS_DELETED: + return new Pair<>(context.getString(R.string.file_deleted), true); + case Transferable.STATUS_FAILED: + return new Pair<>(context.getString(R.string.file_transmission_failed), true); + case Transferable.STATUS_UPLOADING: + if (message.getStatus() == Message.STATUS_OFFERED) { + return new Pair<>(context.getString(R.string.offering_x_file, + getFileDescriptionString(context, message)), true); + } else { + return new Pair<>(context.getString(R.string.sending_x_file, + getFileDescriptionString(context, message)), true); + } + default: + return new Pair<>("", false); + } + } else if (message.getEncryption() == Message.ENCRYPTION_PGP) { + return new Pair<>(context.getString(R.string.pgp_message), true); + } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { + return new Pair<>(context.getString(R.string.decryption_failed), true); + } else if (message.getType() == Message.TYPE_FILE || message.getType() == Message.TYPE_IMAGE) { + if (message.getStatus() == Message.STATUS_RECEIVED) { + return new Pair<>(context.getString(R.string.received_x_file, + getFileDescriptionString(context, message)), true); + } else { + return new Pair<>(getFileDescriptionString(context, message), true); + } + } else { + String body = message.getBody(); if (body.length() > 256) { body = body.substring(0, 256); } if (body.startsWith(Message.ME_COMMAND)) { - return new Pair<>(body.replaceAll("^" + Message.ME_COMMAND, - UIHelper.getMessageDisplayName(message) + " "), false); - } else if (GeoHelper.isGeoUri(message.getBody())) { - if (message.getStatus() == Message.STATUS_RECEIVED) { - return new Pair<>(context.getString(R.string.received_location), true); - } else { - return new Pair<>(context.getString(R.string.location), true); - } + return new Pair<>(body.replaceAll("^" + Message.ME_COMMAND, + UIHelper.getMessageDisplayName(message) + " "), false); + } else if (GeoHelper.isGeoUri(message.getBody())) { + if (message.getStatus() == Message.STATUS_RECEIVED) { + return new Pair<>(context.getString(R.string.received_location), true); + } else { + return new Pair<>(context.getString(R.string.location), true); + } } else if (message.bodyIsXmpp()) { if (message.getStatus() == Message.STATUS_RECEIVED) { return new Pair<>(context.getString(R.string.received_contact), true); } else { return new Pair<>(context.getString(R.string.contact), true); } - } else if (message.treatAsDownloadable() == Message.Decision.MUST) { - return new Pair<>(context.getString(R.string.x_file_offered_for_download, - getFileDescriptionString(context,message)),true); - } else{ - return new Pair<>(body.trim(), false); - } - } - } + } else if (message.treatAsDownloadable() == Message.Decision.MUST) { + return new Pair<>(context.getString(R.string.x_file_offered_for_download, + getFileDescriptionString(context, message)), true); + } else { + return new Pair<>(body.trim(), false); + } + } + } - public static String getFileDescriptionString(final Context context, final Message message) { - if (message.getType() == Message.TYPE_IMAGE) { - return context.getString(R.string.image); - } - final String mime = message.getMimeType(); - if (mime == null) { - return context.getString(R.string.file); - } else if (mime.startsWith("audio/")) { - return context.getString(R.string.audio); - } else if(mime.startsWith("video/")) { - return context.getString(R.string.video); - } else if (mime.startsWith("image/")) { - return context.getString(R.string.image); - } else if (mime.contains("pdf")) { - return context.getString(R.string.pdf_document) ; - } else if (mime.contains("application/vnd.android.package-archive")) { - return context.getString(R.string.apk) ; - } else if (mime.contains("vcard")) { - return context.getString(R.string.vcard) ; - } else { - return mime; - } - } + public static String getFileDescriptionString(final Context context, final Message message) { + if (message.getType() == Message.TYPE_IMAGE) { + return context.getString(R.string.image); + } + final String mime = message.getMimeType(); + if (mime == null) { + return context.getString(R.string.file); + } else if (mime.startsWith("audio/")) { + return context.getString(R.string.audio); + } else if (mime.startsWith("video/")) { + return context.getString(R.string.video); + } else if (mime.startsWith("image/")) { + return context.getString(R.string.image); + } else if (mime.contains("pdf")) { + return context.getString(R.string.pdf_document); + } else if (mime.contains("application/vnd.android.package-archive")) { + return context.getString(R.string.apk); + } else if (mime.contains("vcard")) { + return context.getString(R.string.vcard); + } else { + return mime; + } + } - public static String getMessageDisplayName(final Message message) { - final Conversation conversation = message.getConversation(); - if (message.getStatus() == Message.STATUS_RECEIVED) { - final Contact contact = message.getContact(); - if (conversation.getMode() == Conversation.MODE_MULTI) { - if (contact != null) { - return contact.getDisplayName(); - } else { - return getDisplayedMucCounterpart(message.getCounterpart()); - } - } else { - return contact != null ? contact.getDisplayName() : ""; - } - } else { - if (conversation.getMode() == Conversation.MODE_MULTI) { - return conversation.getMucOptions().getSelf().getName(); - } else { - final Jid jid = conversation.getAccount().getJid(); - return jid.hasLocalpart() ? jid.getLocalpart() : jid.toDomainJid().toString(); - } - } - } + public static String getMessageDisplayName(final Message message) { + final Conversation conversation = message.getConversation(); + if (message.getStatus() == Message.STATUS_RECEIVED) { + final Contact contact = message.getContact(); + if (conversation.getMode() == Conversation.MODE_MULTI) { + if (contact != null) { + return contact.getDisplayName(); + } else { + return getDisplayedMucCounterpart(message.getCounterpart()); + } + } else { + return contact != null ? contact.getDisplayName() : ""; + } + } else { + if (conversation.getMode() == Conversation.MODE_MULTI) { + return conversation.getMucOptions().getSelf().getName(); + } else { + final Jid jid = conversation.getAccount().getJid(); + return jid.hasLocalpart() ? jid.getLocalpart() : jid.toDomainJid().toString(); + } + } + } - public static String getMessageHint(Context context, Conversation conversation) { - switch (conversation.getNextEncryption()) { - case Message.ENCRYPTION_NONE: - if (Config.multipleEncryptionChoices()) { - return context.getString(R.string.send_unencrypted_message); - } else { - return context.getString(R.string.send_message_to_x,conversation.getName()); - } - case Message.ENCRYPTION_OTR: - return context.getString(R.string.send_otr_message); - case Message.ENCRYPTION_AXOLOTL: - AxolotlService axolotlService = conversation.getAccount().getAxolotlService(); - if (axolotlService != null && axolotlService.trustedSessionVerified(conversation)) { - return context.getString(R.string.send_omemo_x509_message); - } else { - return context.getString(R.string.send_omemo_message); - } - case Message.ENCRYPTION_PGP: - return context.getString(R.string.send_pgp_message); - default: - return ""; - } - } + public static String getMessageHint(Context context, Conversation conversation) { + switch (conversation.getNextEncryption()) { + case Message.ENCRYPTION_NONE: + if (Config.multipleEncryptionChoices()) { + return context.getString(R.string.send_unencrypted_message); + } else { + return context.getString(R.string.send_message_to_x, conversation.getName()); + } + case Message.ENCRYPTION_OTR: + return context.getString(R.string.send_otr_message); + case Message.ENCRYPTION_AXOLOTL: + AxolotlService axolotlService = conversation.getAccount().getAxolotlService(); + if (axolotlService != null && axolotlService.trustedSessionVerified(conversation)) { + return context.getString(R.string.send_omemo_x509_message); + } else { + return context.getString(R.string.send_omemo_message); + } + case Message.ENCRYPTION_PGP: + return context.getString(R.string.send_pgp_message); + default: + return ""; + } + } - public static String getDisplayedMucCounterpart(final Jid counterpart) { - if (counterpart==null) { - return ""; - } else if (!counterpart.isBareJid()) { - return counterpart.getResourcepart().trim(); - } else { - return counterpart.toString().trim(); - } - } + public static String getDisplayedMucCounterpart(final Jid counterpart) { + if (counterpart == null) { + return ""; + } else if (!counterpart.isBareJid()) { + return counterpart.getResourcepart().trim(); + } else { + return counterpart.toString().trim(); + } + } - public static boolean receivedLocationQuestion(Message message) { - if (message == null - || message.getStatus() != Message.STATUS_RECEIVED - || message.getType() != Message.TYPE_TEXT) { - return false; - } - String body = message.getBody() == null ? null : message.getBody().trim().toLowerCase(Locale.getDefault()); - body = body.replace("?","").replace("¿",""); - return LOCATION_QUESTIONS.contains(body); - } + public static boolean receivedLocationQuestion(Message message) { + if (message == null + || message.getStatus() != Message.STATUS_RECEIVED + || message.getType() != Message.TYPE_TEXT) { + return false; + } + String body = message.getBody() == null ? null : message.getBody().trim().toLowerCase(Locale.getDefault()); + body = body.replace("?", "").replace("¿", ""); + return LOCATION_QUESTIONS.contains(body); + } - public static ListItem.Tag getTagForStatus(Context context, Presence.Status status) { - switch (status) { - case CHAT: - return new ListItem.Tag(context.getString(R.string.presence_chat), 0xff259b24); - case AWAY: - return new ListItem.Tag(context.getString(R.string.presence_away), 0xffff9800); - case XA: - return new ListItem.Tag(context.getString(R.string.presence_xa), 0xfff44336); - case DND: - return new ListItem.Tag(context.getString(R.string.presence_dnd), 0xfff44336); - default: - return new ListItem.Tag(context.getString(R.string.presence_online), 0xff259b24); - } - } + public static ListItem.Tag getTagForStatus(Context context, Presence.Status status) { + switch (status) { + case CHAT: + return new ListItem.Tag(context.getString(R.string.presence_chat), 0xff259b24); + case AWAY: + return new ListItem.Tag(context.getString(R.string.presence_away), 0xffff9800); + case XA: + return new ListItem.Tag(context.getString(R.string.presence_xa), 0xfff44336); + case DND: + return new ListItem.Tag(context.getString(R.string.presence_dnd), 0xfff44336); + default: + return new ListItem.Tag(context.getString(R.string.presence_online), 0xff259b24); + } + } - public static String tranlasteType(Context context, String type) { - switch (type.toLowerCase()) { - case "pc": - return context.getString(R.string.type_pc); - case "phone": - return context.getString(R.string.type_phone); - case "tablet": - return context.getString(R.string.type_tablet); - case "web": - return context.getString(R.string.type_web); - case "console": - return context.getString(R.string.type_console); - default: - return type; - } - } + public static String tranlasteType(Context context, String type) { + switch (type.toLowerCase()) { + case "pc": + return context.getString(R.string.type_pc); + case "phone": + return context.getString(R.string.type_phone); + case "tablet": + return context.getString(R.string.type_tablet); + case "web": + return context.getString(R.string.type_web); + case "console": + return context.getString(R.string.type_console); + default: + return type; + } + } } diff --git a/src/main/java/de/pixart/messenger/utils/XmlHelper.java b/src/main/java/de/pixart/messenger/utils/XmlHelper.java index 29729f9d1..299deed28 100644 --- a/src/main/java/de/pixart/messenger/utils/XmlHelper.java +++ b/src/main/java/de/pixart/messenger/utils/XmlHelper.java @@ -1,13 +1,13 @@ package de.pixart.messenger.utils; public class XmlHelper { - public static String encodeEntities(String content) { - content = content.replace("&", "&"); - content = content.replace("<", "<"); - content = content.replace(">", ">"); - content = content.replace("\"", """); - content = content.replace("'", "'"); - content = content.replaceAll("[\\p{Cntrl}&&[^\n\t\r]]", ""); - return content; - } + public static String encodeEntities(String content) { + content = content.replace("&", "&"); + content = content.replace("<", "<"); + content = content.replace(">", ">"); + content = content.replace("\"", """); + content = content.replace("'", "'"); + content = content.replaceAll("[\\p{Cntrl}&&[^\n\t\r]]", ""); + return content; + } } diff --git a/src/main/java/de/pixart/messenger/utils/Xmlns.java b/src/main/java/de/pixart/messenger/utils/Xmlns.java index a4022e03e..675d2db80 100644 --- a/src/main/java/de/pixart/messenger/utils/Xmlns.java +++ b/src/main/java/de/pixart/messenger/utils/Xmlns.java @@ -1,10 +1,10 @@ package de.pixart.messenger.utils; public final class Xmlns { - public static final String BLOCKING = "urn:xmpp:blocking"; - 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 = "urn:xmpp:http:upload"; + public static final String BLOCKING = "urn:xmpp:blocking"; + 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 = "urn:xmpp:http:upload"; public static final String STANZA_IDS = "urn:xmpp:sid:0"; } diff --git a/src/main/java/de/pixart/messenger/utils/XmppUri.java b/src/main/java/de/pixart/messenger/utils/XmppUri.java index dc52f6637..51f2b808a 100644 --- a/src/main/java/de/pixart/messenger/utils/XmppUri.java +++ b/src/main/java/de/pixart/messenger/utils/XmppUri.java @@ -13,139 +13,140 @@ import de.pixart.messenger.xmpp.jid.Jid; public class XmppUri { - protected String jid; - protected boolean muc; - protected String fingerprint; - protected List fingerprints = new ArrayList<>(); - public static final String OMEMO_URI_PARAM = "omemo-sid-"; - public static final String OTR_URI_PARAM = "otr-fingerprint"; - - public XmppUri(String uri) { - try { - parse(Uri.parse(uri)); - } catch (IllegalArgumentException e) { - try { - jid = Jid.fromString(uri).toBareJid().toString(); - } catch (InvalidJidException e2) { - jid = null; - } - } - } - - public XmppUri(Uri uri) { - parse(uri); - } - - public static boolean isXmppUri(String uri) { - String scheme = Uri.parse(uri).getScheme(); - return "xmpp".equalsIgnoreCase(scheme); - } - - protected void parse(Uri uri) { - String scheme = uri.getScheme(); - String host = uri.getHost(); - List segments = uri.getPathSegments(); - if ("https".equalsIgnoreCase(scheme) && "jabber.pix-art.de".equalsIgnoreCase(host)) { - if (segments.size() >= 2 && segments.get(1).contains("@")) { - // sample : https://conversations.im/i/foo@bar.com - try { - jid = Jid.fromString(segments.get(1)).toString(); - } catch (Exception e) { - jid = null; - } - } else if (segments.size() >= 3) { - // sample : https://conversations.im/i/foo/bar.com - jid = segments.get(1) + "@" + segments.get(2); - } - muc = segments.size() > 1 && "j".equalsIgnoreCase(segments.get(0)); - } else if ("xmpp".equalsIgnoreCase(scheme)) { - // sample: xmpp:foo@bar.com - muc = "join".equalsIgnoreCase(uri.getQuery()); - if (uri.getAuthority() != null) { - jid = uri.getAuthority(); - } else { - jid = uri.getSchemeSpecificPart().split("\\?")[0]; - } - this.fingerprints = parseFingerprints(uri.getQuery()); - } else if ("imto".equalsIgnoreCase(scheme)) { - // sample: imto://xmpp/foo@bar.com - try { - jid = URLDecoder.decode(uri.getEncodedPath(), "UTF-8").split("/")[1]; - } catch (final UnsupportedEncodingException ignored) { - jid = null; - } - } else { - try { - jid = Jid.fromString(uri.toString()).toBareJid().toString(); - } catch (final InvalidJidException ignored) { - jid = null; - } - } - } - - protected List parseFingerprints(String query) { - List fingerprints = new ArrayList<>(); - String[] pairs = query == null ? new String[0] : query.split(";"); - for(String pair : pairs) { - String[] parts = pair.split("=",2); - if (parts.length == 2) { - String key = parts[0].toLowerCase(Locale.US); - String value = parts[1]; - if (OTR_URI_PARAM.equals(key)) { - fingerprints.add(new Fingerprint(FingerprintType.OTR,value)); - } - if (key.startsWith(OMEMO_URI_PARAM)) { - try { - int id = Integer.parseInt(key.substring(OMEMO_URI_PARAM.length())); - fingerprints.add(new Fingerprint(FingerprintType.OMEMO,value,id)); - } catch (Exception e) { - //ignoring invalid device id - } - } - return null; - } - } - return fingerprints; - } - - public Jid getJid() { - try { - return this.jid == null ? null :Jid.fromString(this.jid.toLowerCase()); - } catch (InvalidJidException e) { - return null; - } - } - - public List getFingerprints() { - return this.fingerprints; - } - - public boolean hasFingerprints() { - return fingerprints.size() > 0; - } - public enum FingerprintType { - OMEMO, - OTR - } - - public static class Fingerprint { - public final FingerprintType type; - public final String fingerprint; - public final int deviceId; - - public Fingerprint(FingerprintType type, String fingerprint) { - this(type, fingerprint, 0); - } - - public Fingerprint(FingerprintType type, String fingerprint, int deviceId) { - this.type = type; - this.fingerprint = fingerprint; - this.deviceId = deviceId; - } - - @Override - public String toString() { - return type.toString()+": "+fingerprint+(deviceId != 0 ? " "+String.valueOf(deviceId) : ""); - } - } + protected String jid; + protected boolean muc; + protected String fingerprint; + protected List fingerprints = new ArrayList<>(); + public static final String OMEMO_URI_PARAM = "omemo-sid-"; + public static final String OTR_URI_PARAM = "otr-fingerprint"; + + public XmppUri(String uri) { + try { + parse(Uri.parse(uri)); + } catch (IllegalArgumentException e) { + try { + jid = Jid.fromString(uri).toBareJid().toString(); + } catch (InvalidJidException e2) { + jid = null; + } + } + } + + public XmppUri(Uri uri) { + parse(uri); + } + + public static boolean isXmppUri(String uri) { + String scheme = Uri.parse(uri).getScheme(); + return "xmpp".equalsIgnoreCase(scheme); + } + + protected void parse(Uri uri) { + String scheme = uri.getScheme(); + String host = uri.getHost(); + List segments = uri.getPathSegments(); + if ("https".equalsIgnoreCase(scheme) && "jabber.pix-art.de".equalsIgnoreCase(host)) { + if (segments.size() >= 2 && segments.get(1).contains("@")) { + // sample : https://conversations.im/i/foo@bar.com + try { + jid = Jid.fromString(segments.get(1)).toString(); + } catch (Exception e) { + jid = null; + } + } else if (segments.size() >= 3) { + // sample : https://conversations.im/i/foo/bar.com + jid = segments.get(1) + "@" + segments.get(2); + } + muc = segments.size() > 1 && "j".equalsIgnoreCase(segments.get(0)); + } else if ("xmpp".equalsIgnoreCase(scheme)) { + // sample: xmpp:foo@bar.com + muc = "join".equalsIgnoreCase(uri.getQuery()); + if (uri.getAuthority() != null) { + jid = uri.getAuthority(); + } else { + jid = uri.getSchemeSpecificPart().split("\\?")[0]; + } + this.fingerprints = parseFingerprints(uri.getQuery()); + } else if ("imto".equalsIgnoreCase(scheme)) { + // sample: imto://xmpp/foo@bar.com + try { + jid = URLDecoder.decode(uri.getEncodedPath(), "UTF-8").split("/")[1]; + } catch (final UnsupportedEncodingException ignored) { + jid = null; + } + } else { + try { + jid = Jid.fromString(uri.toString()).toBareJid().toString(); + } catch (final InvalidJidException ignored) { + jid = null; + } + } + } + + protected List parseFingerprints(String query) { + List fingerprints = new ArrayList<>(); + String[] pairs = query == null ? new String[0] : query.split(";"); + for (String pair : pairs) { + String[] parts = pair.split("=", 2); + if (parts.length == 2) { + String key = parts[0].toLowerCase(Locale.US); + String value = parts[1]; + if (OTR_URI_PARAM.equals(key)) { + fingerprints.add(new Fingerprint(FingerprintType.OTR, value)); + } + if (key.startsWith(OMEMO_URI_PARAM)) { + try { + int id = Integer.parseInt(key.substring(OMEMO_URI_PARAM.length())); + fingerprints.add(new Fingerprint(FingerprintType.OMEMO, value, id)); + } catch (Exception e) { + //ignoring invalid device id + } + } + return null; + } + } + return fingerprints; + } + + public Jid getJid() { + try { + return this.jid == null ? null : Jid.fromString(this.jid.toLowerCase()); + } catch (InvalidJidException e) { + return null; + } + } + + public List getFingerprints() { + return this.fingerprints; + } + + public boolean hasFingerprints() { + return fingerprints.size() > 0; + } + + public enum FingerprintType { + OMEMO, + OTR + } + + public static class Fingerprint { + public final FingerprintType type; + public final String fingerprint; + public final int deviceId; + + public Fingerprint(FingerprintType type, String fingerprint) { + this(type, fingerprint, 0); + } + + public Fingerprint(FingerprintType type, String fingerprint, int deviceId) { + this.type = type; + this.fingerprint = fingerprint; + this.deviceId = deviceId; + } + + @Override + public String toString() { + return type.toString() + ": " + fingerprint + (deviceId != 0 ? " " + String.valueOf(deviceId) : ""); + } + } } diff --git a/src/main/java/de/pixart/messenger/utils/video/MediaController.java b/src/main/java/de/pixart/messenger/utils/video/MediaController.java index 7af113931..2b47afade 100644 --- a/src/main/java/de/pixart/messenger/utils/video/MediaController.java +++ b/src/main/java/de/pixart/messenger/utils/video/MediaController.java @@ -199,7 +199,7 @@ public class MediaController { int rotationValue = Integer.valueOf(rotation); int originalWidth = Integer.valueOf(width); int originalHeight = Integer.valueOf(height); - float ratio = (float)video_width / (float)video_height; // 16:9 = 1,7778, 4:3 = 1,3333 + float ratio = (float) video_width / (float) video_height; // 16:9 = 1,7778, 4:3 = 1,3333 if (video_height > video_width) { resultHeight = Config.VIDEO_SIZE; diff --git a/src/main/java/de/pixart/messenger/utils/video/TextureRenderer.java b/src/main/java/de/pixart/messenger/utils/video/TextureRenderer.java index aa4542fa3..3c53941ea 100644 --- a/src/main/java/de/pixart/messenger/utils/video/TextureRenderer.java +++ b/src/main/java/de/pixart/messenger/utils/video/TextureRenderer.java @@ -43,23 +43,23 @@ public class TextureRenderer { private static final String VERTEX_SHADER = "uniform mat4 uMVPMatrix;\n" + - "uniform mat4 uSTMatrix;\n" + - "attribute vec4 aPosition;\n" + - "attribute vec4 aTextureCoord;\n" + - "varying vec2 vTextureCoord;\n" + - "void main() {\n" + - " gl_Position = uMVPMatrix * aPosition;\n" + - " vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" + - "}\n"; + "uniform mat4 uSTMatrix;\n" + + "attribute vec4 aPosition;\n" + + "attribute vec4 aTextureCoord;\n" + + "varying vec2 vTextureCoord;\n" + + "void main() {\n" + + " gl_Position = uMVPMatrix * aPosition;\n" + + " vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" + + "}\n"; private static final String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n" + - "precision mediump float;\n" + - "varying vec2 vTextureCoord;\n" + - "uniform samplerExternalOES sTexture;\n" + - "void main() {\n" + - " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + - "}\n"; + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform samplerExternalOES sTexture;\n" + + "void main() {\n" + + " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + + "}\n"; private float[] mMVPMatrix = new float[16]; private float[] mSTMatrix = new float[16]; diff --git a/src/main/java/de/pixart/messenger/utils/video/Track.java b/src/main/java/de/pixart/messenger/utils/video/Track.java index 347c65490..18111a675 100644 --- a/src/main/java/de/pixart/messenger/utils/video/Track.java +++ b/src/main/java/de/pixart/messenger/utils/video/Track.java @@ -70,7 +70,7 @@ public class Track { public Track(int id, MediaFormat format, boolean isAudio) throws Exception { trackId = id; if (!isAudio) { - sampleDurations.add((long)3015); + sampleDurations.add((long) 3015); duration = 3015; width = format.getInteger(MediaFormat.KEY_WIDTH); height = format.getInteger(MediaFormat.KEY_HEIGHT); @@ -136,7 +136,7 @@ public class Track { sampleDescriptionBox.addBox(visualSampleEntry); } } else { - sampleDurations.add((long)1024); + sampleDurations.add((long) 1024); duration = 1024; isAudio = true; volume = 1; @@ -167,7 +167,7 @@ public class Track { AudioSpecificConfig audioSpecificConfig = new AudioSpecificConfig(); audioSpecificConfig.setAudioObjectType(2); - audioSpecificConfig.setSamplingFrequencyIndex(samplingFrequencyIndexMap.get((int)audioSampleEntry.getSampleRate())); + audioSpecificConfig.setSamplingFrequencyIndex(samplingFrequencyIndexMap.get((int) audioSampleEntry.getSampleRate())); audioSpecificConfig.setChannelConfiguration(audioSampleEntry.getChannelCount()); decoderConfigDescriptor.setAudioSpecificInfo(audioSpecificConfig); diff --git a/src/main/java/de/pixart/messenger/xml/Element.java b/src/main/java/de/pixart/messenger/xml/Element.java index ca8cdf0a7..7ed08a483 100644 --- a/src/main/java/de/pixart/messenger/xml/Element.java +++ b/src/main/java/de/pixart/messenger/xml/Element.java @@ -12,178 +12,178 @@ import de.pixart.messenger.xmpp.jid.InvalidJidException; import de.pixart.messenger.xmpp.jid.Jid; public class Element { - private final String name; - private Hashtable attributes = new Hashtable<>(); - private String content; - protected List children = new ArrayList<>(); - - public Element(String name) { - this.name = name; - } - - public Element(String name, String xmlns) { - this.name = name; - this.setAttribute("xmlns", xmlns); - } - - public Element addChild(Element child) { - this.content = null; - children.add(child); - return child; - } - - public Element addChild(String name) { - this.content = null; - Element child = new Element(name); - children.add(child); - return child; - } - - public Element addChild(String name, String xmlns) { - this.content = null; - Element child = new Element(name); - child.setAttribute("xmlns", xmlns); - children.add(child); - return child; - } - - public Element setContent(String content) { - this.content = content; - this.children.clear(); - return this; - } - - public Element findChild(String name) { - for (Element child : this.children) { - if (child.getName().equals(name)) { - return child; - } - } - return null; - } - - public String findChildContent(String name) { - Element element = findChild(name); - return element == null ? null : element.getContent(); - } - - public Element findChild(String name, String xmlns) { - for (Element child : this.children) { - if (name.equals(child.getName()) && xmlns.equals(child.getAttribute("xmlns"))) { - return child; - } - } - return null; - } - - public String findChildContent(String name, String xmlns) { - Element element = findChild(name,xmlns); - return element == null ? null : element.getContent(); - } - - public boolean hasChild(final String name) { - return findChild(name) != null; - } - - public boolean hasChild(final String name, final String xmlns) { - return findChild(name, xmlns) != null; - } - - public List getChildren() { - return this.children; - } - - public Element setChildren(List children) { - this.children = children; - return this; - } - - public final String getContent() { - return content; - } - - public Element setAttribute(String name, String value) { - if (name != null && value != null) { - this.attributes.put(name, value); - } - return this; - } - - public Element setAttributes(Hashtable attributes) { - this.attributes = attributes; - return this; - } - - public String getAttribute(String name) { - if (this.attributes.containsKey(name)) { - return this.attributes.get(name); - } else { - return null; - } - } - - public Jid getAttributeAsJid(String name) { - final String jid = this.getAttribute(name); - if (jid != null && !jid.isEmpty()) { - try { - return Jid.fromString(jid); - } catch (final InvalidJidException e) { - Log.e(Config.LOGTAG, "could not parse jid " + jid); - return null; - } - } - return null; - } - - public Hashtable getAttributes() { - return this.attributes; - } - - public String toString() { - StringBuilder elementOutput = new StringBuilder(); - if ((content == null) && (children.size() == 0)) { - Tag emptyTag = Tag.empty(name); - emptyTag.setAtttributes(this.attributes); - elementOutput.append(emptyTag.toString()); - } else { - Tag startTag = Tag.start(name); - startTag.setAtttributes(this.attributes); - elementOutput.append(startTag); - if (content != null) { - elementOutput.append(XmlHelper.encodeEntities(content)); - } else { - for (Element child : children) { - elementOutput.append(child.toString()); - } - } - Tag endTag = Tag.end(name); - elementOutput.append(endTag); - } - return elementOutput.toString(); - } - - public final String getName() { - return name; - } - - public void clearChildren() { - this.children.clear(); - } - - public void setAttribute(String name, long value) { - this.setAttribute(name, Long.toString(value)); - } - - public void setAttribute(String name, int value) { - this.setAttribute(name, Integer.toString(value)); - } - - public boolean getAttributeAsBoolean(String name) { - String attr = getAttribute(name); - return (attr != null && (attr.equalsIgnoreCase("true") || attr.equalsIgnoreCase("1"))); - } - - public String getNamespace() { - return getAttribute("xmlns"); - } + private final String name; + private Hashtable attributes = new Hashtable<>(); + private String content; + protected List children = new ArrayList<>(); + + public Element(String name) { + this.name = name; + } + + public Element(String name, String xmlns) { + this.name = name; + this.setAttribute("xmlns", xmlns); + } + + public Element addChild(Element child) { + this.content = null; + children.add(child); + return child; + } + + public Element addChild(String name) { + this.content = null; + Element child = new Element(name); + children.add(child); + return child; + } + + public Element addChild(String name, String xmlns) { + this.content = null; + Element child = new Element(name); + child.setAttribute("xmlns", xmlns); + children.add(child); + return child; + } + + public Element setContent(String content) { + this.content = content; + this.children.clear(); + return this; + } + + public Element findChild(String name) { + for (Element child : this.children) { + if (child.getName().equals(name)) { + return child; + } + } + return null; + } + + public String findChildContent(String name) { + Element element = findChild(name); + return element == null ? null : element.getContent(); + } + + public Element findChild(String name, String xmlns) { + for (Element child : this.children) { + if (name.equals(child.getName()) && xmlns.equals(child.getAttribute("xmlns"))) { + return child; + } + } + return null; + } + + public String findChildContent(String name, String xmlns) { + Element element = findChild(name, xmlns); + return element == null ? null : element.getContent(); + } + + public boolean hasChild(final String name) { + return findChild(name) != null; + } + + public boolean hasChild(final String name, final String xmlns) { + return findChild(name, xmlns) != null; + } + + public List getChildren() { + return this.children; + } + + public Element setChildren(List children) { + this.children = children; + return this; + } + + public final String getContent() { + return content; + } + + public Element setAttribute(String name, String value) { + if (name != null && value != null) { + this.attributes.put(name, value); + } + return this; + } + + public Element setAttributes(Hashtable attributes) { + this.attributes = attributes; + return this; + } + + public String getAttribute(String name) { + if (this.attributes.containsKey(name)) { + return this.attributes.get(name); + } else { + return null; + } + } + + public Jid getAttributeAsJid(String name) { + final String jid = this.getAttribute(name); + if (jid != null && !jid.isEmpty()) { + try { + return Jid.fromString(jid); + } catch (final InvalidJidException e) { + Log.e(Config.LOGTAG, "could not parse jid " + jid); + return null; + } + } + return null; + } + + public Hashtable getAttributes() { + return this.attributes; + } + + public String toString() { + StringBuilder elementOutput = new StringBuilder(); + if ((content == null) && (children.size() == 0)) { + Tag emptyTag = Tag.empty(name); + emptyTag.setAtttributes(this.attributes); + elementOutput.append(emptyTag.toString()); + } else { + Tag startTag = Tag.start(name); + startTag.setAtttributes(this.attributes); + elementOutput.append(startTag); + if (content != null) { + elementOutput.append(XmlHelper.encodeEntities(content)); + } else { + for (Element child : children) { + elementOutput.append(child.toString()); + } + } + Tag endTag = Tag.end(name); + elementOutput.append(endTag); + } + return elementOutput.toString(); + } + + public final String getName() { + return name; + } + + public void clearChildren() { + this.children.clear(); + } + + public void setAttribute(String name, long value) { + this.setAttribute(name, Long.toString(value)); + } + + public void setAttribute(String name, int value) { + this.setAttribute(name, Integer.toString(value)); + } + + public boolean getAttributeAsBoolean(String name) { + String attr = getAttribute(name); + return (attr != null && (attr.equalsIgnoreCase("true") || attr.equalsIgnoreCase("1"))); + } + + public String getNamespace() { + return getAttribute("xmlns"); + } } diff --git a/src/main/java/de/pixart/messenger/xml/Tag.java b/src/main/java/de/pixart/messenger/xml/Tag.java index b604140f0..8e6944a40 100644 --- a/src/main/java/de/pixart/messenger/xml/Tag.java +++ b/src/main/java/de/pixart/messenger/xml/Tag.java @@ -8,97 +8,97 @@ import java.util.Set; import de.pixart.messenger.utils.XmlHelper; public class Tag { - public static final int NO = -1; - public static final int START = 0; - public static final int END = 1; - public static final int EMPTY = 2; - - protected int type; - protected String name; - protected Hashtable attributes = new Hashtable(); - - protected Tag(int type, String name) { - this.type = type; - this.name = name; - } - - public static Tag no(String text) { - return new Tag(NO, text); - } - - public static Tag start(String name) { - return new Tag(START, name); - } - - public static Tag end(String name) { - return new Tag(END, name); - } - - public static Tag empty(String name) { - return new Tag(EMPTY, name); - } - - public String getName() { - return name; - } - - public String getAttribute(String attrName) { - return this.attributes.get(attrName); - } - - public Tag setAttribute(String attrName, String attrValue) { - this.attributes.put(attrName, attrValue); - return this; - } - - public Tag setAtttributes(Hashtable attributes) { - this.attributes = attributes; - return this; - } - - public boolean isStart(String needle) { - if (needle == null) - return false; - return (this.type == START) && (needle.equals(this.name)); - } - - public boolean isEnd(String needle) { - if (needle == null) - return false; - return (this.type == END) && (needle.equals(this.name)); - } - - public boolean isNo() { - return (this.type == NO); - } - - public String toString() { - StringBuilder tagOutput = new StringBuilder(); - tagOutput.append('<'); - if (type == END) { - tagOutput.append('/'); - } - tagOutput.append(name); - if (type != END) { - Set> attributeSet = attributes.entrySet(); - Iterator> it = attributeSet.iterator(); - while (it.hasNext()) { - Entry entry = it.next(); - tagOutput.append(' '); - tagOutput.append(entry.getKey()); - tagOutput.append("=\""); - tagOutput.append(XmlHelper.encodeEntities(entry.getValue())); - tagOutput.append('"'); - } - } - if (type == EMPTY) { - tagOutput.append('/'); - } - tagOutput.append('>'); - return tagOutput.toString(); - } - - public Hashtable getAttributes() { - return this.attributes; - } + public static final int NO = -1; + public static final int START = 0; + public static final int END = 1; + public static final int EMPTY = 2; + + protected int type; + protected String name; + protected Hashtable attributes = new Hashtable(); + + protected Tag(int type, String name) { + this.type = type; + this.name = name; + } + + public static Tag no(String text) { + return new Tag(NO, text); + } + + public static Tag start(String name) { + return new Tag(START, name); + } + + public static Tag end(String name) { + return new Tag(END, name); + } + + public static Tag empty(String name) { + return new Tag(EMPTY, name); + } + + public String getName() { + return name; + } + + public String getAttribute(String attrName) { + return this.attributes.get(attrName); + } + + public Tag setAttribute(String attrName, String attrValue) { + this.attributes.put(attrName, attrValue); + return this; + } + + public Tag setAtttributes(Hashtable attributes) { + this.attributes = attributes; + return this; + } + + public boolean isStart(String needle) { + if (needle == null) + return false; + return (this.type == START) && (needle.equals(this.name)); + } + + public boolean isEnd(String needle) { + if (needle == null) + return false; + return (this.type == END) && (needle.equals(this.name)); + } + + public boolean isNo() { + return (this.type == NO); + } + + public String toString() { + StringBuilder tagOutput = new StringBuilder(); + tagOutput.append('<'); + if (type == END) { + tagOutput.append('/'); + } + tagOutput.append(name); + if (type != END) { + Set> attributeSet = attributes.entrySet(); + Iterator> it = attributeSet.iterator(); + while (it.hasNext()) { + Entry entry = it.next(); + tagOutput.append(' '); + tagOutput.append(entry.getKey()); + tagOutput.append("=\""); + tagOutput.append(XmlHelper.encodeEntities(entry.getValue())); + tagOutput.append('"'); + } + } + if (type == EMPTY) { + tagOutput.append('/'); + } + tagOutput.append('>'); + return tagOutput.toString(); + } + + public Hashtable getAttributes() { + return this.attributes; + } } diff --git a/src/main/java/de/pixart/messenger/xml/TagWriter.java b/src/main/java/de/pixart/messenger/xml/TagWriter.java index 61220773a..0a663ce1b 100644 --- a/src/main/java/de/pixart/messenger/xml/TagWriter.java +++ b/src/main/java/de/pixart/messenger/xml/TagWriter.java @@ -9,96 +9,96 @@ import de.pixart.messenger.xmpp.stanzas.AbstractStanza; public class TagWriter { - private OutputStreamWriter outputStream; - private boolean finshed = false; - private LinkedBlockingQueue writeQueue = new LinkedBlockingQueue(); - private Thread asyncStanzaWriter = new Thread() { - private boolean shouldStop = false; + private OutputStreamWriter outputStream; + private boolean finshed = false; + private LinkedBlockingQueue writeQueue = new LinkedBlockingQueue(); + private Thread asyncStanzaWriter = new Thread() { + private boolean shouldStop = false; - @Override - public void run() { - while (!shouldStop) { - if ((finshed) && (writeQueue.size() == 0)) { - return; - } - try { - AbstractStanza output = writeQueue.take(); - outputStream.write(output.toString()); - outputStream.flush(); - } catch (Exception e) { - shouldStop = true; - } - } - } - }; + @Override + public void run() { + while (!shouldStop) { + if ((finshed) && (writeQueue.size() == 0)) { + return; + } + try { + AbstractStanza output = writeQueue.take(); + outputStream.write(output.toString()); + outputStream.flush(); + } catch (Exception e) { + shouldStop = true; + } + } + } + }; - public TagWriter() { - } + public TagWriter() { + } - public void setOutputStream(OutputStream out) throws IOException { - if (out == null) { - throw new IOException(); - } - this.outputStream = new OutputStreamWriter(out); - } + public void setOutputStream(OutputStream out) throws IOException { + if (out == null) { + throw new IOException(); + } + this.outputStream = new OutputStreamWriter(out); + } - public TagWriter beginDocument() throws IOException { - if (outputStream == null) { - throw new IOException("output stream was null"); - } - outputStream.write(""); - outputStream.flush(); - return this; - } + public TagWriter beginDocument() throws IOException { + if (outputStream == null) { + throw new IOException("output stream was null"); + } + outputStream.write(""); + outputStream.flush(); + return this; + } - public TagWriter writeTag(Tag tag) throws IOException { - if (outputStream == null) { - throw new IOException("output stream was null"); - } - outputStream.write(tag.toString()); - outputStream.flush(); - return this; - } + public TagWriter writeTag(Tag tag) throws IOException { + if (outputStream == null) { + throw new IOException("output stream was null"); + } + outputStream.write(tag.toString()); + outputStream.flush(); + return this; + } - public TagWriter writeElement(Element element) throws IOException { - if (outputStream == null) { - throw new IOException("output stream was null"); - } - outputStream.write(element.toString()); - outputStream.flush(); - return this; - } + public TagWriter writeElement(Element element) throws IOException { + if (outputStream == null) { + throw new IOException("output stream was null"); + } + outputStream.write(element.toString()); + outputStream.flush(); + return this; + } - public TagWriter writeStanzaAsync(AbstractStanza stanza) { - if (finshed) { - return this; - } else { - if (!asyncStanzaWriter.isAlive()) { - try { - asyncStanzaWriter.start(); - } catch (IllegalThreadStateException e) { - // already started - } - } - writeQueue.add(stanza); - return this; - } - } + public TagWriter writeStanzaAsync(AbstractStanza stanza) { + if (finshed) { + return this; + } else { + if (!asyncStanzaWriter.isAlive()) { + try { + asyncStanzaWriter.start(); + } catch (IllegalThreadStateException e) { + // already started + } + } + writeQueue.add(stanza); + return this; + } + } - public void finish() { - this.finshed = true; - } + public void finish() { + this.finshed = true; + } - public boolean finished() { - return (this.writeQueue.size() == 0); - } + public boolean finished() { + return (this.writeQueue.size() == 0); + } - public boolean isActive() { - return outputStream != null; - } + public boolean isActive() { + return outputStream != null; + } - public void forceClose() { - finish(); - outputStream = null; - } + public void forceClose() { + finish(); + outputStream = null; + } } diff --git a/src/main/java/de/pixart/messenger/xml/XmlReader.java b/src/main/java/de/pixart/messenger/xml/XmlReader.java index 0b7796c6c..3c8cfa1f2 100644 --- a/src/main/java/de/pixart/messenger/xml/XmlReader.java +++ b/src/main/java/de/pixart/messenger/xml/XmlReader.java @@ -15,111 +15,111 @@ import java.io.InputStreamReader; import de.pixart.messenger.Config; public class XmlReader { - private XmlPullParser parser; - private PowerManager.WakeLock wakeLock; - private InputStream is; + private XmlPullParser parser; + private PowerManager.WakeLock wakeLock; + private InputStream is; - public XmlReader(WakeLock wakeLock) { - this.parser = Xml.newPullParser(); - try { - this.parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); - } catch (XmlPullParserException e) { - Log.d(Config.LOGTAG, "error setting namespace feature on parser"); - } - this.wakeLock = wakeLock; - } + public XmlReader(WakeLock wakeLock) { + this.parser = Xml.newPullParser(); + try { + this.parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); + } catch (XmlPullParserException e) { + Log.d(Config.LOGTAG, "error setting namespace feature on parser"); + } + this.wakeLock = wakeLock; + } - public void setInputStream(InputStream inputStream) throws IOException { - if (inputStream == null) { - throw new IOException(); - } - this.is = inputStream; - try { - parser.setInput(new InputStreamReader(this.is)); - } catch (XmlPullParserException e) { - throw new IOException("error resetting parser"); - } - } + public void setInputStream(InputStream inputStream) throws IOException { + if (inputStream == null) { + throw new IOException(); + } + this.is = inputStream; + try { + parser.setInput(new InputStreamReader(this.is)); + } catch (XmlPullParserException e) { + throw new IOException("error resetting parser"); + } + } - public void reset() throws IOException { - if (this.is == null) { - throw new IOException(); - } - try { - parser.setInput(new InputStreamReader(this.is)); - } catch (XmlPullParserException e) { - throw new IOException("error resetting parser"); - } - } + public void reset() throws IOException { + if (this.is == null) { + throw new IOException(); + } + try { + parser.setInput(new InputStreamReader(this.is)); + } catch (XmlPullParserException e) { + throw new IOException("error resetting parser"); + } + } - public Tag readTag() throws XmlPullParserException, IOException { - if (wakeLock.isHeld()) { - try { - wakeLock.release(); - } catch (RuntimeException re) { - Log.d(Config.LOGTAG,"runtime exception releasing wakelock before reading tag "+re.getMessage()); - } - } - try { - while (this.is != null && parser.next() != XmlPullParser.END_DOCUMENT) { - wakeLock.acquire(); - if (parser.getEventType() == XmlPullParser.START_TAG) { - Tag tag = Tag.start(parser.getName()); - for (int i = 0; i < parser.getAttributeCount(); ++i) { - tag.setAttribute(parser.getAttributeName(i), - parser.getAttributeValue(i)); - } - String xmlns = parser.getNamespace(); - if (xmlns != null) { - tag.setAttribute("xmlns", xmlns); - } - return tag; - } else if (parser.getEventType() == XmlPullParser.END_TAG) { - return Tag.end(parser.getName()); - } else if (parser.getEventType() == XmlPullParser.TEXT) { - return Tag.no(parser.getText()); - } - } + public Tag readTag() throws XmlPullParserException, IOException { + if (wakeLock.isHeld()) { + try { + wakeLock.release(); + } catch (RuntimeException re) { + Log.d(Config.LOGTAG, "runtime exception releasing wakelock before reading tag " + re.getMessage()); + } + } + try { + while (this.is != null && parser.next() != XmlPullParser.END_DOCUMENT) { + wakeLock.acquire(); + if (parser.getEventType() == XmlPullParser.START_TAG) { + Tag tag = Tag.start(parser.getName()); + for (int i = 0; i < parser.getAttributeCount(); ++i) { + tag.setAttribute(parser.getAttributeName(i), + parser.getAttributeValue(i)); + } + String xmlns = parser.getNamespace(); + if (xmlns != null) { + tag.setAttribute("xmlns", xmlns); + } + return tag; + } else if (parser.getEventType() == XmlPullParser.END_TAG) { + return Tag.end(parser.getName()); + } else if (parser.getEventType() == XmlPullParser.TEXT) { + return Tag.no(parser.getText()); + } + } - } catch (Throwable throwable) { - throw new IOException("xml parser mishandled "+throwable.getClass().getName(), throwable); - } finally { - if (wakeLock.isHeld()) { - try { - wakeLock.release(); - } catch (RuntimeException re) { - Log.d(Config.LOGTAG,"runtime exception releasing wakelock after exception "+re.getMessage()); - } - } - } - return null; - } + } catch (Throwable throwable) { + throw new IOException("xml parser mishandled " + throwable.getClass().getName(), throwable); + } finally { + if (wakeLock.isHeld()) { + try { + wakeLock.release(); + } catch (RuntimeException re) { + Log.d(Config.LOGTAG, "runtime exception releasing wakelock after exception " + re.getMessage()); + } + } + } + return null; + } - public Element readElement(Tag currentTag) throws XmlPullParserException, - IOException { - Element element = new Element(currentTag.getName()); - element.setAttributes(currentTag.getAttributes()); - Tag nextTag = this.readTag(); - if (nextTag == null) { - throw new IOException("interrupted mid tag"); - } - if (nextTag.isNo()) { - element.setContent(nextTag.getName()); - nextTag = this.readTag(); - if (nextTag == null) { - throw new IOException("interrupted mid tag"); - } - } - while (!nextTag.isEnd(element.getName())) { - if (!nextTag.isNo()) { - Element child = this.readElement(nextTag); - element.addChild(child); - } - nextTag = this.readTag(); - if (nextTag == null) { - throw new IOException("interrupted mid tag"); - } - } - return element; - } + public Element readElement(Tag currentTag) throws XmlPullParserException, + IOException { + Element element = new Element(currentTag.getName()); + element.setAttributes(currentTag.getAttributes()); + Tag nextTag = this.readTag(); + if (nextTag == null) { + throw new IOException("interrupted mid tag"); + } + if (nextTag.isNo()) { + element.setContent(nextTag.getName()); + nextTag = this.readTag(); + if (nextTag == null) { + throw new IOException("interrupted mid tag"); + } + } + while (!nextTag.isEnd(element.getName())) { + if (!nextTag.isNo()) { + Element child = this.readElement(nextTag); + element.addChild(child); + } + nextTag = this.readTag(); + if (nextTag == null) { + throw new IOException("interrupted mid tag"); + } + } + return element; + } } diff --git a/src/main/java/de/pixart/messenger/xmpp/OnAdvancedStreamFeaturesLoaded.java b/src/main/java/de/pixart/messenger/xmpp/OnAdvancedStreamFeaturesLoaded.java index 572b61bc1..3ca11c068 100644 --- a/src/main/java/de/pixart/messenger/xmpp/OnAdvancedStreamFeaturesLoaded.java +++ b/src/main/java/de/pixart/messenger/xmpp/OnAdvancedStreamFeaturesLoaded.java @@ -3,5 +3,5 @@ package de.pixart.messenger.xmpp; import de.pixart.messenger.entities.Account; public interface OnAdvancedStreamFeaturesLoaded { - public void onAdvancedStreamFeaturesAvailable(final Account account); + public void onAdvancedStreamFeaturesAvailable(final Account account); } diff --git a/src/main/java/de/pixart/messenger/xmpp/OnBindListener.java b/src/main/java/de/pixart/messenger/xmpp/OnBindListener.java index cafc3fadb..710a807dd 100644 --- a/src/main/java/de/pixart/messenger/xmpp/OnBindListener.java +++ b/src/main/java/de/pixart/messenger/xmpp/OnBindListener.java @@ -3,5 +3,5 @@ package de.pixart.messenger.xmpp; import de.pixart.messenger.entities.Account; public interface OnBindListener { - public void onBind(Account account); + public void onBind(Account account); } diff --git a/src/main/java/de/pixart/messenger/xmpp/OnContactStatusChanged.java b/src/main/java/de/pixart/messenger/xmpp/OnContactStatusChanged.java index be1c51b72..66cd24858 100644 --- a/src/main/java/de/pixart/messenger/xmpp/OnContactStatusChanged.java +++ b/src/main/java/de/pixart/messenger/xmpp/OnContactStatusChanged.java @@ -3,5 +3,5 @@ package de.pixart.messenger.xmpp; import de.pixart.messenger.entities.Contact; public interface OnContactStatusChanged { - public void onContactStatusChanged(final Contact contact, final boolean online); + public void onContactStatusChanged(final Contact contact, final boolean online); } diff --git a/src/main/java/de/pixart/messenger/xmpp/OnIqPacketReceived.java b/src/main/java/de/pixart/messenger/xmpp/OnIqPacketReceived.java index b07b6258e..797eba155 100644 --- a/src/main/java/de/pixart/messenger/xmpp/OnIqPacketReceived.java +++ b/src/main/java/de/pixart/messenger/xmpp/OnIqPacketReceived.java @@ -4,5 +4,5 @@ import de.pixart.messenger.entities.Account; import de.pixart.messenger.xmpp.stanzas.IqPacket; public interface OnIqPacketReceived extends PacketReceived { - public void onIqPacketReceived(Account account, IqPacket packet); + public void onIqPacketReceived(Account account, IqPacket packet); } diff --git a/src/main/java/de/pixart/messenger/xmpp/OnKeyStatusUpdated.java b/src/main/java/de/pixart/messenger/xmpp/OnKeyStatusUpdated.java index 3727d37d7..925a2544d 100644 --- a/src/main/java/de/pixart/messenger/xmpp/OnKeyStatusUpdated.java +++ b/src/main/java/de/pixart/messenger/xmpp/OnKeyStatusUpdated.java @@ -3,5 +3,5 @@ package de.pixart.messenger.xmpp; import de.pixart.messenger.crypto.axolotl.AxolotlService; public interface OnKeyStatusUpdated { - public void onKeyStatusUpdated(AxolotlService.FetchStatus report); + public void onKeyStatusUpdated(AxolotlService.FetchStatus report); } diff --git a/src/main/java/de/pixart/messenger/xmpp/OnMessageAcknowledged.java b/src/main/java/de/pixart/messenger/xmpp/OnMessageAcknowledged.java index 3ab3bd75a..7ff5658a8 100644 --- a/src/main/java/de/pixart/messenger/xmpp/OnMessageAcknowledged.java +++ b/src/main/java/de/pixart/messenger/xmpp/OnMessageAcknowledged.java @@ -3,5 +3,5 @@ package de.pixart.messenger.xmpp; import de.pixart.messenger.entities.Account; public interface OnMessageAcknowledged { - public void onMessageAcknowledged(Account account, String id); + public void onMessageAcknowledged(Account account, String id); } diff --git a/src/main/java/de/pixart/messenger/xmpp/OnMessagePacketReceived.java b/src/main/java/de/pixart/messenger/xmpp/OnMessagePacketReceived.java index 9a0f3bb2b..d4d54f8df 100644 --- a/src/main/java/de/pixart/messenger/xmpp/OnMessagePacketReceived.java +++ b/src/main/java/de/pixart/messenger/xmpp/OnMessagePacketReceived.java @@ -4,5 +4,5 @@ import de.pixart.messenger.entities.Account; import de.pixart.messenger.xmpp.stanzas.MessagePacket; public interface OnMessagePacketReceived extends PacketReceived { - public void onMessagePacketReceived(Account account, MessagePacket packet); + public void onMessagePacketReceived(Account account, MessagePacket packet); } diff --git a/src/main/java/de/pixart/messenger/xmpp/OnPresencePacketReceived.java b/src/main/java/de/pixart/messenger/xmpp/OnPresencePacketReceived.java index 5419443a2..2df725ff7 100644 --- a/src/main/java/de/pixart/messenger/xmpp/OnPresencePacketReceived.java +++ b/src/main/java/de/pixart/messenger/xmpp/OnPresencePacketReceived.java @@ -4,5 +4,5 @@ import de.pixart.messenger.entities.Account; import de.pixart.messenger.xmpp.stanzas.PresencePacket; public interface OnPresencePacketReceived extends PacketReceived { - public void onPresencePacketReceived(Account account, PresencePacket packet); + public void onPresencePacketReceived(Account account, PresencePacket packet); } diff --git a/src/main/java/de/pixart/messenger/xmpp/OnStatusChanged.java b/src/main/java/de/pixart/messenger/xmpp/OnStatusChanged.java index 7f1e07b9c..40b7b313a 100644 --- a/src/main/java/de/pixart/messenger/xmpp/OnStatusChanged.java +++ b/src/main/java/de/pixart/messenger/xmpp/OnStatusChanged.java @@ -3,5 +3,5 @@ package de.pixart.messenger.xmpp; import de.pixart.messenger.entities.Account; public interface OnStatusChanged { - public void onStatusChanged(Account account); + public void onStatusChanged(Account account); } diff --git a/src/main/java/de/pixart/messenger/xmpp/OnUpdateBlocklist.java b/src/main/java/de/pixart/messenger/xmpp/OnUpdateBlocklist.java index 4b8d90ec7..bf02e2f29 100644 --- a/src/main/java/de/pixart/messenger/xmpp/OnUpdateBlocklist.java +++ b/src/main/java/de/pixart/messenger/xmpp/OnUpdateBlocklist.java @@ -1,13 +1,13 @@ package de.pixart.messenger.xmpp; public interface OnUpdateBlocklist { - // Use an enum instead of a boolean to make sure we don't run into the boolean trap - // (`onUpdateBlocklist(true)' doesn't read well, and could be confusing). - public static enum Status { - BLOCKED, - UNBLOCKED - } + // Use an enum instead of a boolean to make sure we don't run into the boolean trap + // (`onUpdateBlocklist(true)' doesn't read well, and could be confusing). + public static enum Status { + BLOCKED, + UNBLOCKED + } - @SuppressWarnings("MethodNameSameAsClassName") - public void OnUpdateBlocklist(final Status status); + @SuppressWarnings("MethodNameSameAsClassName") + public void OnUpdateBlocklist(final Status status); } diff --git a/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java b/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java index 2642ee84a..bcb5904a7 100644 --- a/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java +++ b/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java @@ -94,784 +94,783 @@ import de.pixart.messenger.xmpp.stanzas.streammgmt.ResumePacket; public class XmppConnection implements Runnable { - private static final int PACKET_IQ = 0; - private static final int PACKET_MESSAGE = 1; - private static final int PACKET_PRESENCE = 2; - protected Account account; - private final WakeLock wakeLock; - private Socket socket; - private XmlReader tagReader; - private TagWriter tagWriter; - private final Features features = new Features(this); - private boolean needsBinding = true; - private boolean shouldAuthenticate = true; - private Element streamFeatures; - private final HashMap disco = new HashMap<>(); - - private String streamId = null; - private int smVersion = 3; - private final SparseArray mStanzaQueue = new SparseArray<>(); - - private int stanzasReceived = 0; - private int stanzasSent = 0; - private long lastPacketReceived = 0; - private long lastPingSent = 0; - private long lastConnect = 0; - private long lastSessionStarted = 0; - private long lastDiscoStarted = 0; - private AtomicInteger mPendingServiceDiscoveries = new AtomicInteger(0); - private AtomicBoolean mWaitForDisco = new AtomicBoolean(true); - private boolean mInteractive = false; - private int attempt = 0; + private static final int PACKET_IQ = 0; + private static final int PACKET_MESSAGE = 1; + private static final int PACKET_PRESENCE = 2; + protected Account account; + private final WakeLock wakeLock; + private Socket socket; + private XmlReader tagReader; + private TagWriter tagWriter; + private final Features features = new Features(this); + private boolean needsBinding = true; + private boolean shouldAuthenticate = true; + private Element streamFeatures; + private final HashMap disco = new HashMap<>(); + + private String streamId = null; + private int smVersion = 3; + private final SparseArray mStanzaQueue = new SparseArray<>(); + + private int stanzasReceived = 0; + private int stanzasSent = 0; + private long lastPacketReceived = 0; + private long lastPingSent = 0; + private long lastConnect = 0; + private long lastSessionStarted = 0; + private long lastDiscoStarted = 0; + private AtomicInteger mPendingServiceDiscoveries = new AtomicInteger(0); + private AtomicBoolean mWaitForDisco = new AtomicBoolean(true); + private boolean mInteractive = false; + private int attempt = 0; public static String errorMessage = null; - private final Hashtable> packetCallbacks = new Hashtable<>(); - private OnPresencePacketReceived presenceListener = null; - private OnJinglePacketReceived jingleListener = null; - private OnIqPacketReceived unregisteredIqListener = null; - private OnMessagePacketReceived messageListener = null; - private OnStatusChanged statusListener = null; - private OnBindListener bindListener = null; - private final ArrayList advancedStreamFeaturesLoadedListeners = new ArrayList<>(); - private OnMessageAcknowledged acknowledgedListener = null; - private XmppConnectionService mXmppConnectionService = null; + private final Hashtable> packetCallbacks = new Hashtable<>(); + private OnPresencePacketReceived presenceListener = null; + private OnJinglePacketReceived jingleListener = null; + private OnIqPacketReceived unregisteredIqListener = null; + private OnMessagePacketReceived messageListener = null; + private OnStatusChanged statusListener = null; + private OnBindListener bindListener = null; + private final ArrayList advancedStreamFeaturesLoadedListeners = new ArrayList<>(); + private OnMessageAcknowledged acknowledgedListener = null; + private XmppConnectionService mXmppConnectionService = null; private EditAccountActivity mEditAccountActivity = null; - private SaslMechanism saslMechanism; + private SaslMechanism saslMechanism; private class MyKeyManager implements X509KeyManager { - @Override - public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) { - return account.getPrivateKeyAlias(); - } - - @Override - public String chooseServerAlias(String s, Principal[] principals, Socket socket) { - return null; - } - - @Override - public X509Certificate[] getCertificateChain(String alias) { - Log.d(Config.LOGTAG,"getting certificate chain"); - try { - return KeyChain.getCertificateChain(mXmppConnectionService, alias); - } catch (Exception e) { - Log.d(Config.LOGTAG,e.getMessage()); - return new X509Certificate[0]; - } - } - - @Override - public String[] getClientAliases(String s, Principal[] principals) { - return new String[0]; - } - - @Override - public String[] getServerAliases(String s, Principal[] principals) { - return new String[0]; - } - - @Override - public PrivateKey getPrivateKey(String alias) { - try { - return KeyChain.getPrivateKey(mXmppConnectionService, alias); - } catch (Exception e) { - return null; - } - } - } - - private Identity mServerIdentity = Identity.UNKNOWN; - - public final OnIqPacketReceived registrationResponseListener = new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - account.setOption(Account.OPTION_REGISTER, false); - forceCloseSocket(); - changeStatus(Account.State.REGISTRATION_SUCCESSFUL); - } else { - final List PASSWORD_TOO_WEAK_MSGS = Arrays.asList( - "The password is too weak", - "Please use a longer password."); - final Element error = packet.findChild("error"); - Account.State state = Account.State.REGISTRATION_FAILED; + @Override + public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) { + return account.getPrivateKeyAlias(); + } + + @Override + public String chooseServerAlias(String s, Principal[] principals, Socket socket) { + return null; + } + + @Override + public X509Certificate[] getCertificateChain(String alias) { + Log.d(Config.LOGTAG, "getting certificate chain"); + try { + return KeyChain.getCertificateChain(mXmppConnectionService, alias); + } catch (Exception e) { + Log.d(Config.LOGTAG, e.getMessage()); + return new X509Certificate[0]; + } + } + + @Override + public String[] getClientAliases(String s, Principal[] principals) { + return new String[0]; + } + + @Override + public String[] getServerAliases(String s, Principal[] principals) { + return new String[0]; + } + + @Override + public PrivateKey getPrivateKey(String alias) { + try { + return KeyChain.getPrivateKey(mXmppConnectionService, alias); + } catch (Exception e) { + return null; + } + } + } + + private Identity mServerIdentity = Identity.UNKNOWN; + + public final OnIqPacketReceived registrationResponseListener = new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.RESULT) { + account.setOption(Account.OPTION_REGISTER, false); + forceCloseSocket(); + changeStatus(Account.State.REGISTRATION_SUCCESSFUL); + } else { + final List PASSWORD_TOO_WEAK_MSGS = Arrays.asList( + "The password is too weak", + "Please use a longer password."); + final Element error = packet.findChild("error"); + Account.State state = Account.State.REGISTRATION_FAILED; mXmppConnectionService.deleteAccount(account); - if (error != null) { + if (error != null) { if (error.hasChild("text")) { errorMessage = error.findChildContent("text"); Log.d(Config.LOGTAG, "Error creating account : " + error.findChildContent("text")); } - if (error.hasChild("conflict")) { - state = Account.State.REGISTRATION_CONFLICT; - } else if (error.hasChild("resource-constraint") - && "wait".equals(error.getAttribute("type"))) { - state = Account.State.REGISTRATION_PLEASE_WAIT; - } else if (error.hasChild("not-acceptable") - && PASSWORD_TOO_WEAK_MSGS.contains(error.findChildContent("text"))) { - state = Account.State.REGISTRATION_PASSWORD_TOO_WEAK; - } - } + if (error.hasChild("conflict")) { + state = Account.State.REGISTRATION_CONFLICT; + } else if (error.hasChild("resource-constraint") + && "wait".equals(error.getAttribute("type"))) { + state = Account.State.REGISTRATION_PLEASE_WAIT; + } else if (error.hasChild("not-acceptable") + && PASSWORD_TOO_WEAK_MSGS.contains(error.findChildContent("text"))) { + state = Account.State.REGISTRATION_PASSWORD_TOO_WEAK; + } + } Log.d(Config.LOGTAG, "Delete account because of error " + error); - changeStatus(state); - forceCloseSocket(); - } - } - }; - - public XmppConnection(final Account account, final XmppConnectionService service) { - this.account = account; - this.wakeLock = service.getPowerManager().newWakeLock( - PowerManager.PARTIAL_WAKE_LOCK, account.getJid().toBareJid().toString()); - tagWriter = new TagWriter(); - mXmppConnectionService = service; - } - - protected void changeStatus(final Account.State nextStatus) { - if (account.getStatus() != nextStatus) { - if ((nextStatus == Account.State.OFFLINE) - && (account.getStatus() != Account.State.CONNECTING) - && (account.getStatus() != Account.State.ONLINE) - && (account.getStatus() != Account.State.DISABLED)) { - return; - } - if (nextStatus == Account.State.ONLINE) { - this.attempt = 0; - } - account.setStatus(nextStatus); - if (statusListener != null) { - statusListener.onStatusChanged(account); - } - } - } - - public void prepareNewConnection() { - this.lastConnect = SystemClock.elapsedRealtime(); - this.lastPingSent = SystemClock.elapsedRealtime(); - this.lastDiscoStarted = Long.MAX_VALUE; - this.changeStatus(Account.State.CONNECTING); - } - - protected void connect() { + changeStatus(state); + forceCloseSocket(); + } + } + }; + + public XmppConnection(final Account account, final XmppConnectionService service) { + this.account = account; + this.wakeLock = service.getPowerManager().newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, account.getJid().toBareJid().toString()); + tagWriter = new TagWriter(); + mXmppConnectionService = service; + } + + protected void changeStatus(final Account.State nextStatus) { + if (account.getStatus() != nextStatus) { + if ((nextStatus == Account.State.OFFLINE) + && (account.getStatus() != Account.State.CONNECTING) + && (account.getStatus() != Account.State.ONLINE) + && (account.getStatus() != Account.State.DISABLED)) { + return; + } + if (nextStatus == Account.State.ONLINE) { + this.attempt = 0; + } + account.setStatus(nextStatus); + if (statusListener != null) { + statusListener.onStatusChanged(account); + } + } + } + + public void prepareNewConnection() { + this.lastConnect = SystemClock.elapsedRealtime(); + this.lastPingSent = SystemClock.elapsedRealtime(); + this.lastDiscoStarted = Long.MAX_VALUE; + this.changeStatus(Account.State.CONNECTING); + } + + protected void connect() { if (mXmppConnectionService.areMessagesInitialized()) { mXmppConnectionService.resetSendingToWaiting(account); } - Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": connecting"); - features.encryptionEnabled = false; - this.attempt++; - switch (account.getJid().getDomainpart()) { - case "chat.facebook.com": - mServerIdentity = Identity.FACEBOOK; - break; - case "nimbuzz.com": - mServerIdentity = Identity.NIMBUZZ; - break; - default: - mServerIdentity = Identity.UNKNOWN; - break; - } - try { - shouldAuthenticate = needsBinding = !account.isOptionSet(Account.OPTION_REGISTER); - tagReader = new XmlReader(wakeLock); - tagWriter = new TagWriter(); - this.changeStatus(Account.State.CONNECTING); - final boolean useTor = mXmppConnectionService.useTorToConnect() || account.isOnion(); - final boolean extended = mXmppConnectionService.showExtendedConnectionOptions(); - if (Config.XMPP_IP != null && Config.XMPP_Ports != null) { - Integer[] XMPP_Port = Config.XMPP_Ports; - Integer Port = XMPP_Port[new Random().nextInt(XMPP_Port.length)]; - socket = new Socket(); - try { - socket.connect(new InetSocketAddress(Config.XMPP_IP, Port), Config.SOCKET_TIMEOUT * 1000); - } catch (IOException e) { - throw new IOException(); - } + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": connecting"); + features.encryptionEnabled = false; + this.attempt++; + switch (account.getJid().getDomainpart()) { + case "chat.facebook.com": + mServerIdentity = Identity.FACEBOOK; + break; + case "nimbuzz.com": + mServerIdentity = Identity.NIMBUZZ; + break; + default: + mServerIdentity = Identity.UNKNOWN; + break; + } + try { + shouldAuthenticate = needsBinding = !account.isOptionSet(Account.OPTION_REGISTER); + tagReader = new XmlReader(wakeLock); + tagWriter = new TagWriter(); + this.changeStatus(Account.State.CONNECTING); + final boolean useTor = mXmppConnectionService.useTorToConnect() || account.isOnion(); + final boolean extended = mXmppConnectionService.showExtendedConnectionOptions(); + if (Config.XMPP_IP != null && Config.XMPP_Ports != null) { + Integer[] XMPP_Port = Config.XMPP_Ports; + Integer Port = XMPP_Port[new Random().nextInt(XMPP_Port.length)]; + socket = new Socket(); + try { + socket.connect(new InetSocketAddress(Config.XMPP_IP, Port), Config.SOCKET_TIMEOUT * 1000); + } catch (IOException e) { + throw new IOException(); + } Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": connect to " + Config.XMPP_IP + ":" + Port); - startXmpp(); - } - else if (useTor) { - String destination; - if (account.getHostname() == null || account.getHostname().isEmpty()) { - destination = account.getServer().toString(); - } else { - destination = account.getHostname(); - } - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": connect to " + destination + " via Tor"); - socket = SocksSocketFactory.createSocketOverTor(destination, account.getPort()); - startXmpp(); - } else if (extended && account.getHostname() != null && !account.getHostname().isEmpty()) { - - InetSocketAddress address = new InetSocketAddress(account.getHostname(), account.getPort()); - - features.encryptionEnabled = account.getPort() == 5223; - - try { - if (features.encryptionEnabled) { - try { - final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier(); - socket = tlsFactoryVerifier.factory.createSocket(); - socket.connect(address, Config.SOCKET_TIMEOUT * 1000); - final SSLSession session = ((SSLSocket) socket).getSession(); - if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), session)) { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed"); - throw new SecurityException(); - } - } catch (KeyManagementException e) { - features.encryptionEnabled = false; - socket = new Socket(); - } - } else { - socket = new Socket(); - socket.connect(address, Config.SOCKET_TIMEOUT * 1000); - } - } catch (IOException e) { - throw new UnknownHostException(); - } - startXmpp(); - } else if (DNSHelper.isIp(account.getServer().toString())) { - socket = new Socket(); - try { - socket.connect(new InetSocketAddress(account.getServer().toString(), 5222), Config.SOCKET_TIMEOUT * 1000); - } catch (IOException e) { - throw new UnknownHostException(); - } - startXmpp(); - } else { - final Bundle result = DNSHelper.getSRVRecord(account.getServer(), mXmppConnectionService); - final ArrayList values = result.getParcelableArrayList("values"); - for (Iterator iterator = values.iterator(); iterator.hasNext(); ) { - if (Thread.currentThread().isInterrupted()) { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": Thread was interrupted"); - return; - } - final Bundle namePort = (Bundle) iterator.next(); - try { - String srvRecordServer; - try { - srvRecordServer = IDN.toASCII(namePort.getString("name")); - } catch (final IllegalArgumentException e) { - // TODO: Handle me?` - srvRecordServer = ""; - } - final int srvRecordPort = namePort.getInt("port"); - final String srvIpServer = namePort.getString("ip"); - // if tls is true, encryption is implied and must not be started - features.encryptionEnabled = namePort.getBoolean("tls"); - final InetSocketAddress addr; - if (srvIpServer != null) { - addr = new InetSocketAddress(srvIpServer, srvRecordPort); - Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() - + ": using values from dns " + srvRecordServer - + "[" + srvIpServer + "]:" + srvRecordPort + " tls: " + features.encryptionEnabled); - } else { - addr = new InetSocketAddress(srvRecordServer, srvRecordPort); - Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() - + ": using values from dns " - + srvRecordServer + ":" + srvRecordPort + " tls: " + features.encryptionEnabled); - } - - if (!features.encryptionEnabled) { - socket = new Socket(); - socket.connect(addr, Config.SOCKET_TIMEOUT * 1000); - } else { - final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier(); - socket = tlsFactoryVerifier.factory.createSocket(); - - if (socket == null) { - throw new IOException("could not initialize ssl socket"); - } - - SSLSocketHelper.setSecurity((SSLSocket) socket); - SSLSocketHelper.setSNIHost(tlsFactoryVerifier.factory, (SSLSocket) socket, account.getServer().getDomainpart()); - SSLSocketHelper.setAlpnProtocol(tlsFactoryVerifier.factory, (SSLSocket) socket, "xmpp-client"); - - socket.connect(addr, Config.SOCKET_TIMEOUT * 1000); - - if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), ((SSLSocket) socket).getSession())) { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed"); - throw new SecurityException(); - } - } - - if (startXmpp()) - break; // successfully connected to server that speaks xmpp - } catch (final SecurityException e) { - throw e; - } catch (final Throwable e) { - Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage() + "(" + e.getClass().getName() + ")"); - if (!iterator.hasNext()) { - throw new UnknownHostException(); - } - } - } - } - processStream(); - } catch (final java.lang.SecurityException e) { - this.changeStatus(Account.State.MISSING_INTERNET_PERMISSION); - } catch (final IncompatibleServerException e) { - this.changeStatus(Account.State.INCOMPATIBLE_SERVER); - } catch (final SecurityException e) { - this.changeStatus(Account.State.SECURITY_ERROR); - } catch (final UnauthorizedException e) { - this.changeStatus(Account.State.UNAUTHORIZED); - } catch (final PaymentRequiredException e) { - this.changeStatus(Account.State.PAYMENT_REQUIRED); - } catch (final UnknownHostException | ConnectException e) { - this.changeStatus(Account.State.SERVER_NOT_FOUND); - } catch (final SocksSocketFactory.SocksProxyNotFoundException e) { - this.changeStatus(Account.State.TOR_NOT_AVAILABLE); - } catch(final StreamErrorHostUnknown e) { - this.changeStatus(Account.State.HOST_UNKNOWN); - } catch(final StreamErrorPolicyViolation e) { - this.changeStatus(Account.State.POLICY_VIOLATION); - } catch(final StreamError e) { - this.changeStatus(Account.State.STREAM_ERROR); - } catch (final IOException | XmlPullParserException | NoSuchAlgorithmException e) { - Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage()); - this.changeStatus(Account.State.OFFLINE); - this.attempt = Math.max(0, this.attempt - 1); - } finally { - forceCloseSocket(); - if (wakeLock.isHeld()) { - try { - wakeLock.release(); - } catch (final RuntimeException ignored) { - } - } - } - } - - /** - * Starts xmpp protocol, call after connecting to socket - * @return true if server returns with valid xmpp, false otherwise - * @throws IOException Unknown tag on connect - * @throws XmlPullParserException Bad Xml - * @throws NoSuchAlgorithmException Other error + startXmpp(); + } else if (useTor) { + String destination; + if (account.getHostname() == null || account.getHostname().isEmpty()) { + destination = account.getServer().toString(); + } else { + destination = account.getHostname(); + } + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": connect to " + destination + " via Tor"); + socket = SocksSocketFactory.createSocketOverTor(destination, account.getPort()); + startXmpp(); + } else if (extended && account.getHostname() != null && !account.getHostname().isEmpty()) { + + InetSocketAddress address = new InetSocketAddress(account.getHostname(), account.getPort()); + + features.encryptionEnabled = account.getPort() == 5223; + + try { + if (features.encryptionEnabled) { + try { + final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier(); + socket = tlsFactoryVerifier.factory.createSocket(); + socket.connect(address, Config.SOCKET_TIMEOUT * 1000); + final SSLSession session = ((SSLSocket) socket).getSession(); + if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), session)) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed"); + throw new SecurityException(); + } + } catch (KeyManagementException e) { + features.encryptionEnabled = false; + socket = new Socket(); + } + } else { + socket = new Socket(); + socket.connect(address, Config.SOCKET_TIMEOUT * 1000); + } + } catch (IOException e) { + throw new UnknownHostException(); + } + startXmpp(); + } else if (DNSHelper.isIp(account.getServer().toString())) { + socket = new Socket(); + try { + socket.connect(new InetSocketAddress(account.getServer().toString(), 5222), Config.SOCKET_TIMEOUT * 1000); + } catch (IOException e) { + throw new UnknownHostException(); + } + startXmpp(); + } else { + final Bundle result = DNSHelper.getSRVRecord(account.getServer(), mXmppConnectionService); + final ArrayList values = result.getParcelableArrayList("values"); + for (Iterator iterator = values.iterator(); iterator.hasNext(); ) { + if (Thread.currentThread().isInterrupted()) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": Thread was interrupted"); + return; + } + final Bundle namePort = (Bundle) iterator.next(); + try { + String srvRecordServer; + try { + srvRecordServer = IDN.toASCII(namePort.getString("name")); + } catch (final IllegalArgumentException e) { + // TODO: Handle me?` + srvRecordServer = ""; + } + final int srvRecordPort = namePort.getInt("port"); + final String srvIpServer = namePort.getString("ip"); + // if tls is true, encryption is implied and must not be started + features.encryptionEnabled = namePort.getBoolean("tls"); + final InetSocketAddress addr; + if (srvIpServer != null) { + addr = new InetSocketAddress(srvIpServer, srvRecordPort); + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + + ": using values from dns " + srvRecordServer + + "[" + srvIpServer + "]:" + srvRecordPort + " tls: " + features.encryptionEnabled); + } else { + addr = new InetSocketAddress(srvRecordServer, srvRecordPort); + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + + ": using values from dns " + + srvRecordServer + ":" + srvRecordPort + " tls: " + features.encryptionEnabled); + } + + if (!features.encryptionEnabled) { + socket = new Socket(); + socket.connect(addr, Config.SOCKET_TIMEOUT * 1000); + } else { + final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier(); + socket = tlsFactoryVerifier.factory.createSocket(); + + if (socket == null) { + throw new IOException("could not initialize ssl socket"); + } + + SSLSocketHelper.setSecurity((SSLSocket) socket); + SSLSocketHelper.setSNIHost(tlsFactoryVerifier.factory, (SSLSocket) socket, account.getServer().getDomainpart()); + SSLSocketHelper.setAlpnProtocol(tlsFactoryVerifier.factory, (SSLSocket) socket, "xmpp-client"); + + socket.connect(addr, Config.SOCKET_TIMEOUT * 1000); + + if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), ((SSLSocket) socket).getSession())) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed"); + throw new SecurityException(); + } + } + + if (startXmpp()) + break; // successfully connected to server that speaks xmpp + } catch (final SecurityException e) { + throw e; + } catch (final Throwable e) { + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage() + "(" + e.getClass().getName() + ")"); + if (!iterator.hasNext()) { + throw new UnknownHostException(); + } + } + } + } + processStream(); + } catch (final java.lang.SecurityException e) { + this.changeStatus(Account.State.MISSING_INTERNET_PERMISSION); + } catch (final IncompatibleServerException e) { + this.changeStatus(Account.State.INCOMPATIBLE_SERVER); + } catch (final SecurityException e) { + this.changeStatus(Account.State.SECURITY_ERROR); + } catch (final UnauthorizedException e) { + this.changeStatus(Account.State.UNAUTHORIZED); + } catch (final PaymentRequiredException e) { + this.changeStatus(Account.State.PAYMENT_REQUIRED); + } catch (final UnknownHostException | ConnectException e) { + this.changeStatus(Account.State.SERVER_NOT_FOUND); + } catch (final SocksSocketFactory.SocksProxyNotFoundException e) { + this.changeStatus(Account.State.TOR_NOT_AVAILABLE); + } catch (final StreamErrorHostUnknown e) { + this.changeStatus(Account.State.HOST_UNKNOWN); + } catch (final StreamErrorPolicyViolation e) { + this.changeStatus(Account.State.POLICY_VIOLATION); + } catch (final StreamError e) { + this.changeStatus(Account.State.STREAM_ERROR); + } catch (final IOException | XmlPullParserException | NoSuchAlgorithmException e) { + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage()); + this.changeStatus(Account.State.OFFLINE); + this.attempt = Math.max(0, this.attempt - 1); + } finally { + forceCloseSocket(); + if (wakeLock.isHeld()) { + try { + wakeLock.release(); + } catch (final RuntimeException ignored) { + } + } + } + } + + /** + * Starts xmpp protocol, call after connecting to socket + * + * @return true if server returns with valid xmpp, false otherwise + * @throws IOException Unknown tag on connect + * @throws XmlPullParserException Bad Xml + * @throws NoSuchAlgorithmException Other error */ - private boolean startXmpp() throws IOException, XmlPullParserException, NoSuchAlgorithmException { - tagWriter.setOutputStream(socket.getOutputStream()); - tagReader.setInputStream(socket.getInputStream()); - tagWriter.beginDocument(); - sendStartStream(); - Tag nextTag; - while ((nextTag = tagReader.readTag()) != null) { - if (nextTag.isStart("stream")) { - return true; - } else { - throw new IOException("unknown tag on connect"); - } - } - if (socket.isConnected()) { - socket.close(); - } - return false; - } - - private static class TlsFactoryVerifier { - private final SSLSocketFactory factory; - private final HostnameVerifier verifier; - - public TlsFactoryVerifier(final SSLSocketFactory factory, final HostnameVerifier verifier) throws IOException { - this.factory = factory; - this.verifier = verifier; - if (factory == null || verifier == null) { - throw new IOException("could not setup ssl"); - } - } - } - - private TlsFactoryVerifier getTlsFactoryVerifier() throws NoSuchAlgorithmException, KeyManagementException, IOException { - final SSLContext sc = SSLSocketHelper.getSSLContext(); - MemorizingTrustManager trustManager = this.mXmppConnectionService.getMemorizingTrustManager(); - KeyManager[] keyManager; - if (account.getPrivateKeyAlias() != null && account.getPassword().isEmpty()) { - keyManager = new KeyManager[]{new MyKeyManager()}; - } else { - keyManager = null; - } - sc.init(keyManager, new X509TrustManager[]{mInteractive ? trustManager : trustManager.getNonInteractive()}, mXmppConnectionService.getRNG()); - final SSLSocketFactory factory = sc.getSocketFactory(); - final HostnameVerifier verifier; - if (mInteractive) { - verifier = trustManager.wrapHostnameVerifier(new XmppDomainVerifier()); - } else { - verifier = trustManager.wrapHostnameVerifierNonInteractive(new XmppDomainVerifier()); - } - - return new TlsFactoryVerifier(factory, verifier); - } - - @Override - public void run() { - forceCloseSocket(); - connect(); - } - - private void processStream() throws XmlPullParserException, IOException, NoSuchAlgorithmException { - Tag nextTag = tagReader.readTag(); - while (nextTag != null && !nextTag.isEnd("stream")) { - if (nextTag.isStart("error")) { - processStreamError(nextTag); - } else if (nextTag.isStart("features")) { - processStreamFeatures(nextTag); - } else if (nextTag.isStart("proceed")) { - switchOverToTls(nextTag); - } else if (nextTag.isStart("success")) { - final String challenge = tagReader.readElement(nextTag).getContent(); - try { - saslMechanism.getResponse(challenge); - } catch (final SaslMechanism.AuthenticationException e) { - disconnect(true); - Log.e(Config.LOGTAG, String.valueOf(e)); - } - Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": logged in"); - account.setKey(Account.PINNED_MECHANISM_KEY, - String.valueOf(saslMechanism.getPriority())); - tagReader.reset(); - sendStartStream(); - final Tag tag = tagReader.readTag(); - if (tag != null && tag.isStart("stream")) { - processStream(); - } else { - throw new IOException("server didn't restart stream after successful auth"); - } - break; - } else if (nextTag.isStart("failure")) { - final Element failure = tagReader.readElement(nextTag); - final String text = failure.findChildContent("text"); - if (failure.hasChild("account-disabled") - && text != null - && text.contains("renew") - && Config.MAGIC_CREATE_DOMAIN != null - && text.contains(Config.MAGIC_CREATE_DOMAIN)) { - throw new PaymentRequiredException(); - } else { - throw new UnauthorizedException(); - } - } else if (nextTag.isStart("challenge")) { - final String challenge = tagReader.readElement(nextTag).getContent(); - final Element response = new Element("response"); - response.setAttribute("xmlns", - "urn:ietf:params:xml:ns:xmpp-sasl"); - try { - response.setContent(saslMechanism.getResponse(challenge)); - } catch (final SaslMechanism.AuthenticationException e) { - // TODO: Send auth abort tag. - Log.e(Config.LOGTAG, e.toString()); - } - tagWriter.writeElement(response); - } else if (nextTag.isStart("enabled")) { - final Element enabled = tagReader.readElement(nextTag); - if ("true".equals(enabled.getAttribute("resume"))) { - this.streamId = enabled.getAttribute("id"); - Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() - + ": stream management(" + smVersion - + ") enabled (resumable)"); - } else { - Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() - + ": stream management(" + smVersion + ") enabled"); - } - this.stanzasReceived = 0; - final RequestPacket r = new RequestPacket(smVersion); - tagWriter.writeStanzaAsync(r); - } else if (nextTag.isStart("resumed")) { - lastPacketReceived = SystemClock.elapsedRealtime(); - final Element resumed = tagReader.readElement(nextTag); - final String h = resumed.getAttribute("h"); - try { - ArrayList failedStanzas = new ArrayList<>(); - synchronized (this.mStanzaQueue) { - final int serverCount = Integer.parseInt(h); - if (serverCount != stanzasSent) { - Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() - + ": session resumed with lost packages"); - stanzasSent = serverCount; - } else { - Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": session resumed"); - } - acknowledgeStanzaUpTo(serverCount); - for (int i = 0; i < this.mStanzaQueue.size(); ++i) { - failedStanzas.add(mStanzaQueue.valueAt(i)); - } - mStanzaQueue.clear(); - } - Log.d(Config.LOGTAG, "resending " + failedStanzas.size() + " stanzas"); - for (AbstractAcknowledgeableStanza packet : failedStanzas) { - if (packet instanceof MessagePacket) { - MessagePacket message = (MessagePacket) packet; - mXmppConnectionService.markMessage(account, - message.getTo().toBareJid(), - message.getId(), - Message.STATUS_UNSEND); - } - sendPacket(packet); - } - } catch (final NumberFormatException ignored) { - } - Log.d(Config.LOGTAG, account.getJid().toBareJid()+ ": online with resource " + account.getResource()); - changeStatus(Account.State.ONLINE); - } else if (nextTag.isStart("r")) { - tagReader.readElement(nextTag); - if (Config.EXTENDED_SM_LOGGING) { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": acknowledging stanza #" + this.stanzasReceived); - } - final AckPacket ack = new AckPacket(this.stanzasReceived, smVersion); - tagWriter.writeStanzaAsync(ack); - } else if (nextTag.isStart("a")) { - final Element ack = tagReader.readElement(nextTag); - lastPacketReceived = SystemClock.elapsedRealtime(); - try { - synchronized (this.mStanzaQueue) { - final int serverSequence = Integer.parseInt(ack.getAttribute("h")); - acknowledgeStanzaUpTo(serverSequence); - } - } catch (NumberFormatException | NullPointerException e) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": server send ack without sequence number"); - } - } else if (nextTag.isStart("failed")) { - Element failed = tagReader.readElement(nextTag); - try { - final int serverCount = Integer.parseInt(failed.getAttribute("h")); - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": resumption failed but server acknowledged stanza #"+serverCount); - synchronized (this.mStanzaQueue) { - acknowledgeStanzaUpTo(serverCount); - } - } catch (NumberFormatException | NullPointerException e) { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": resumption failed"); - } - resetStreamId(); - if (account.getStatus() != Account.State.ONLINE) { - sendBindRequest(); - } - } else if (nextTag.isStart("iq")) { - processIq(nextTag); - } else if (nextTag.isStart("message")) { - processMessage(nextTag); - } else if (nextTag.isStart("presence")) { - processPresence(nextTag); - } - nextTag = tagReader.readTag(); - } - } - - private void acknowledgeStanzaUpTo(int serverCount) { - for (int i = 0; i < mStanzaQueue.size(); ++i) { - if (serverCount >= mStanzaQueue.keyAt(i)) { - if (Config.EXTENDED_SM_LOGGING) { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": server acknowledged stanza #" + mStanzaQueue.keyAt(i)); - } - AbstractAcknowledgeableStanza stanza = mStanzaQueue.valueAt(i); - if (stanza instanceof MessagePacket && acknowledgedListener != null) { - MessagePacket packet = (MessagePacket) stanza; - acknowledgedListener.onMessageAcknowledged(account, packet.getId()); - } - mStanzaQueue.removeAt(i); - i--; - } - } - } - - private Element processPacket(final Tag currentTag, final int packetType) - throws XmlPullParserException, IOException { - Element element; - switch (packetType) { - case PACKET_IQ: - element = new IqPacket(); - break; - case PACKET_MESSAGE: - element = new MessagePacket(); - break; - case PACKET_PRESENCE: - element = new PresencePacket(); - break; - default: - return null; - } - element.setAttributes(currentTag.getAttributes()); - Tag nextTag = tagReader.readTag(); - if (nextTag == null) { - throw new IOException("interrupted mid tag"); - } - while (!nextTag.isEnd(element.getName())) { - if (!nextTag.isNo()) { - final Element child = tagReader.readElement(nextTag); - final String type = currentTag.getAttribute("type"); - if (packetType == PACKET_IQ - && "jingle".equals(child.getName()) - && ("set".equalsIgnoreCase(type) || "get" - .equalsIgnoreCase(type))) { - element = new JinglePacket(); - element.setAttributes(currentTag.getAttributes()); - } - element.addChild(child); - } - nextTag = tagReader.readTag(); - if (nextTag == null) { - throw new IOException("interrupted mid tag"); - } - } - if (stanzasReceived == Integer.MAX_VALUE) { - resetStreamId(); - throw new IOException("time to restart the session. cant handle >2 billion pcks"); - } - ++stanzasReceived; - lastPacketReceived = SystemClock.elapsedRealtime(); - if (Config.BACKGROUND_STANZA_LOGGING && mXmppConnectionService.checkListeners()) { - Log.d(Config.LOGTAG,"[background stanza] "+element); - } - return element; - } - - private void processIq(final Tag currentTag) throws XmlPullParserException, IOException { - final IqPacket packet = (IqPacket) processPacket(currentTag, PACKET_IQ); - - if (packet.getId() == null) { - return; // an iq packet without id is definitely invalid - } - - if (packet instanceof JinglePacket) { - if (this.jingleListener != null) { - this.jingleListener.onJinglePacketReceived(account,(JinglePacket) packet); - } - } else { - OnIqPacketReceived callback = null; - synchronized (this.packetCallbacks) { - if (packetCallbacks.containsKey(packet.getId())) { - final Pair packetCallbackDuple = packetCallbacks.get(packet.getId()); - // Packets to the server should have responses from the server - if (packetCallbackDuple.first.toServer(account)) { - if (packet.fromServer(account) || mServerIdentity == Identity.FACEBOOK) { - callback = packetCallbackDuple.second; - packetCallbacks.remove(packet.getId()); - } else { - Log.e(Config.LOGTAG, account.getJid().toBareJid().toString() + ": ignoring spoofed iq packet"); - } - } else { - if (packet.getFrom().equals(packetCallbackDuple.first.getTo())) { - callback = packetCallbackDuple.second; - packetCallbacks.remove(packet.getId()); - } else { - Log.e(Config.LOGTAG, account.getJid().toBareJid().toString() + ": ignoring spoofed iq packet"); - } - } - } else if (packet.getType() == IqPacket.TYPE.GET || packet.getType() == IqPacket.TYPE.SET) { - callback = this.unregisteredIqListener; - } - } - if (callback != null) { - callback.onIqPacketReceived(account,packet); - } - } - } - - private void processMessage(final Tag currentTag) throws XmlPullParserException, IOException { - final MessagePacket packet = (MessagePacket) processPacket(currentTag,PACKET_MESSAGE); - this.messageListener.onMessagePacketReceived(account, packet); - } - - private void processPresence(final Tag currentTag) throws XmlPullParserException, IOException { - PresencePacket packet = (PresencePacket) processPacket(currentTag, PACKET_PRESENCE); - this.presenceListener.onPresencePacketReceived(account, packet); - } - - private void sendStartTLS() throws IOException { - final Tag startTLS = Tag.empty("starttls"); - startTLS.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-tls"); - tagWriter.writeTag(startTLS); - } - - - - private void switchOverToTls(final Tag currentTag) throws XmlPullParserException, IOException { - tagReader.readTag(); - try { - final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier(); - final InetAddress address = socket == null ? null : socket.getInetAddress(); - - if (address == null) { - throw new IOException("could not setup ssl"); - } - - final SSLSocket sslSocket = (SSLSocket) tlsFactoryVerifier.factory.createSocket(socket, address.getHostAddress(), socket.getPort(), true); - - if (sslSocket == null) { - throw new IOException("could not initialize ssl socket"); - } - - SSLSocketHelper.setSecurity(sslSocket); - - if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), sslSocket.getSession())) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": TLS certificate verification failed"); - throw new SecurityException(); - } - tagReader.setInputStream(sslSocket.getInputStream()); - tagWriter.setOutputStream(sslSocket.getOutputStream()); - sendStartStream(); - Log.d(Config.LOGTAG, account.getJid().toBareJid()+ ": TLS connection established"); - features.encryptionEnabled = true; - final Tag tag = tagReader.readTag(); - if (tag != null && tag.isStart("stream")) { - processStream(); - } else { - throw new IOException("server didn't restart stream after STARTTLS"); - } - sslSocket.close(); - } catch (final NoSuchAlgorithmException | KeyManagementException e1) { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed"); - throw new SecurityException(); - } - } - - private void processStreamFeatures(final Tag currentTag) - throws XmlPullParserException, IOException { - this.streamFeatures = tagReader.readElement(currentTag); - if (this.streamFeatures.hasChild("starttls") && !features.encryptionEnabled) { - sendStartTLS(); - } else if (this.streamFeatures.hasChild("register") && account.isOptionSet(Account.OPTION_REGISTER)) { - if (features.encryptionEnabled || Config.ALLOW_NON_TLS_CONNECTIONS) { - sendRegistryRequest(); - } else { - throw new IncompatibleServerException(); - } - } else if (!this.streamFeatures.hasChild("register") - && account.isOptionSet(Account.OPTION_REGISTER)) { - forceCloseSocket(); - changeStatus(Account.State.REGISTRATION_NOT_SUPPORTED); - } 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) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": resuming after stanza #"+stanzasReceived); - } - final ResumePacket resume = new ResumePacket(this.streamId, stanzasReceived, smVersion); - this.tagWriter.writeStanzaAsync(resume); - } else if (needsBinding) { - if (this.streamFeatures.hasChild("bind")) { - sendBindRequest(); - } else { - throw new IncompatibleServerException(); - } - } - } - - private void authenticate() throws IOException { - final List 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()); - } else if (mechanisms.contains("ANONYMOUS")) { - saslMechanism = new Anonymous(tagWriter, account, mXmppConnectionService.getRNG()); - } - if (saslMechanism != null) { + private boolean startXmpp() throws IOException, XmlPullParserException, NoSuchAlgorithmException { + tagWriter.setOutputStream(socket.getOutputStream()); + tagReader.setInputStream(socket.getInputStream()); + tagWriter.beginDocument(); + sendStartStream(); + Tag nextTag; + while ((nextTag = tagReader.readTag()) != null) { + if (nextTag.isStart("stream")) { + return true; + } else { + throw new IOException("unknown tag on connect"); + } + } + if (socket.isConnected()) { + socket.close(); + } + return false; + } + + private static class TlsFactoryVerifier { + private final SSLSocketFactory factory; + private final HostnameVerifier verifier; + + public TlsFactoryVerifier(final SSLSocketFactory factory, final HostnameVerifier verifier) throws IOException { + this.factory = factory; + this.verifier = verifier; + if (factory == null || verifier == null) { + throw new IOException("could not setup ssl"); + } + } + } + + private TlsFactoryVerifier getTlsFactoryVerifier() throws NoSuchAlgorithmException, KeyManagementException, IOException { + final SSLContext sc = SSLSocketHelper.getSSLContext(); + MemorizingTrustManager trustManager = this.mXmppConnectionService.getMemorizingTrustManager(); + KeyManager[] keyManager; + if (account.getPrivateKeyAlias() != null && account.getPassword().isEmpty()) { + keyManager = new KeyManager[]{new MyKeyManager()}; + } else { + keyManager = null; + } + sc.init(keyManager, new X509TrustManager[]{mInteractive ? trustManager : trustManager.getNonInteractive()}, mXmppConnectionService.getRNG()); + final SSLSocketFactory factory = sc.getSocketFactory(); + final HostnameVerifier verifier; + if (mInteractive) { + verifier = trustManager.wrapHostnameVerifier(new XmppDomainVerifier()); + } else { + verifier = trustManager.wrapHostnameVerifierNonInteractive(new XmppDomainVerifier()); + } + + return new TlsFactoryVerifier(factory, verifier); + } + + @Override + public void run() { + forceCloseSocket(); + connect(); + } + + private void processStream() throws XmlPullParserException, IOException, NoSuchAlgorithmException { + Tag nextTag = tagReader.readTag(); + while (nextTag != null && !nextTag.isEnd("stream")) { + if (nextTag.isStart("error")) { + processStreamError(nextTag); + } else if (nextTag.isStart("features")) { + processStreamFeatures(nextTag); + } else if (nextTag.isStart("proceed")) { + switchOverToTls(nextTag); + } else if (nextTag.isStart("success")) { + final String challenge = tagReader.readElement(nextTag).getContent(); + try { + saslMechanism.getResponse(challenge); + } catch (final SaslMechanism.AuthenticationException e) { + disconnect(true); + Log.e(Config.LOGTAG, String.valueOf(e)); + } + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": logged in"); + account.setKey(Account.PINNED_MECHANISM_KEY, + String.valueOf(saslMechanism.getPriority())); + tagReader.reset(); + sendStartStream(); + final Tag tag = tagReader.readTag(); + if (tag != null && tag.isStart("stream")) { + processStream(); + } else { + throw new IOException("server didn't restart stream after successful auth"); + } + break; + } else if (nextTag.isStart("failure")) { + final Element failure = tagReader.readElement(nextTag); + final String text = failure.findChildContent("text"); + if (failure.hasChild("account-disabled") + && text != null + && text.contains("renew") + && Config.MAGIC_CREATE_DOMAIN != null + && text.contains(Config.MAGIC_CREATE_DOMAIN)) { + throw new PaymentRequiredException(); + } else { + throw new UnauthorizedException(); + } + } else if (nextTag.isStart("challenge")) { + final String challenge = tagReader.readElement(nextTag).getContent(); + final Element response = new Element("response"); + response.setAttribute("xmlns", + "urn:ietf:params:xml:ns:xmpp-sasl"); + try { + response.setContent(saslMechanism.getResponse(challenge)); + } catch (final SaslMechanism.AuthenticationException e) { + // TODO: Send auth abort tag. + Log.e(Config.LOGTAG, e.toString()); + } + tagWriter.writeElement(response); + } else if (nextTag.isStart("enabled")) { + final Element enabled = tagReader.readElement(nextTag); + if ("true".equals(enabled.getAttribute("resume"))) { + this.streamId = enabled.getAttribute("id"); + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + + ": stream management(" + smVersion + + ") enabled (resumable)"); + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + + ": stream management(" + smVersion + ") enabled"); + } + this.stanzasReceived = 0; + final RequestPacket r = new RequestPacket(smVersion); + tagWriter.writeStanzaAsync(r); + } else if (nextTag.isStart("resumed")) { + lastPacketReceived = SystemClock.elapsedRealtime(); + final Element resumed = tagReader.readElement(nextTag); + final String h = resumed.getAttribute("h"); + try { + ArrayList failedStanzas = new ArrayList<>(); + synchronized (this.mStanzaQueue) { + final int serverCount = Integer.parseInt(h); + if (serverCount != stanzasSent) { + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + + ": session resumed with lost packages"); + stanzasSent = serverCount; + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": session resumed"); + } + acknowledgeStanzaUpTo(serverCount); + for (int i = 0; i < this.mStanzaQueue.size(); ++i) { + failedStanzas.add(mStanzaQueue.valueAt(i)); + } + mStanzaQueue.clear(); + } + Log.d(Config.LOGTAG, "resending " + failedStanzas.size() + " stanzas"); + for (AbstractAcknowledgeableStanza packet : failedStanzas) { + if (packet instanceof MessagePacket) { + MessagePacket message = (MessagePacket) packet; + mXmppConnectionService.markMessage(account, + message.getTo().toBareJid(), + message.getId(), + Message.STATUS_UNSEND); + } + sendPacket(packet); + } + } catch (final NumberFormatException ignored) { + } + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": online with resource " + account.getResource()); + changeStatus(Account.State.ONLINE); + } else if (nextTag.isStart("r")) { + tagReader.readElement(nextTag); + if (Config.EXTENDED_SM_LOGGING) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": acknowledging stanza #" + this.stanzasReceived); + } + final AckPacket ack = new AckPacket(this.stanzasReceived, smVersion); + tagWriter.writeStanzaAsync(ack); + } else if (nextTag.isStart("a")) { + final Element ack = tagReader.readElement(nextTag); + lastPacketReceived = SystemClock.elapsedRealtime(); + try { + synchronized (this.mStanzaQueue) { + final int serverSequence = Integer.parseInt(ack.getAttribute("h")); + acknowledgeStanzaUpTo(serverSequence); + } + } catch (NumberFormatException | NullPointerException e) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": server send ack without sequence number"); + } + } else if (nextTag.isStart("failed")) { + Element failed = tagReader.readElement(nextTag); + try { + final int serverCount = Integer.parseInt(failed.getAttribute("h")); + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": resumption failed but server acknowledged stanza #" + serverCount); + synchronized (this.mStanzaQueue) { + acknowledgeStanzaUpTo(serverCount); + } + } catch (NumberFormatException | NullPointerException e) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": resumption failed"); + } + resetStreamId(); + if (account.getStatus() != Account.State.ONLINE) { + sendBindRequest(); + } + } else if (nextTag.isStart("iq")) { + processIq(nextTag); + } else if (nextTag.isStart("message")) { + processMessage(nextTag); + } else if (nextTag.isStart("presence")) { + processPresence(nextTag); + } + nextTag = tagReader.readTag(); + } + } + + private void acknowledgeStanzaUpTo(int serverCount) { + for (int i = 0; i < mStanzaQueue.size(); ++i) { + if (serverCount >= mStanzaQueue.keyAt(i)) { + if (Config.EXTENDED_SM_LOGGING) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": server acknowledged stanza #" + mStanzaQueue.keyAt(i)); + } + AbstractAcknowledgeableStanza stanza = mStanzaQueue.valueAt(i); + if (stanza instanceof MessagePacket && acknowledgedListener != null) { + MessagePacket packet = (MessagePacket) stanza; + acknowledgedListener.onMessageAcknowledged(account, packet.getId()); + } + mStanzaQueue.removeAt(i); + i--; + } + } + } + + private Element processPacket(final Tag currentTag, final int packetType) + throws XmlPullParserException, IOException { + Element element; + switch (packetType) { + case PACKET_IQ: + element = new IqPacket(); + break; + case PACKET_MESSAGE: + element = new MessagePacket(); + break; + case PACKET_PRESENCE: + element = new PresencePacket(); + break; + default: + return null; + } + element.setAttributes(currentTag.getAttributes()); + Tag nextTag = tagReader.readTag(); + if (nextTag == null) { + throw new IOException("interrupted mid tag"); + } + while (!nextTag.isEnd(element.getName())) { + if (!nextTag.isNo()) { + final Element child = tagReader.readElement(nextTag); + final String type = currentTag.getAttribute("type"); + if (packetType == PACKET_IQ + && "jingle".equals(child.getName()) + && ("set".equalsIgnoreCase(type) || "get" + .equalsIgnoreCase(type))) { + element = new JinglePacket(); + element.setAttributes(currentTag.getAttributes()); + } + element.addChild(child); + } + nextTag = tagReader.readTag(); + if (nextTag == null) { + throw new IOException("interrupted mid tag"); + } + } + if (stanzasReceived == Integer.MAX_VALUE) { + resetStreamId(); + throw new IOException("time to restart the session. cant handle >2 billion pcks"); + } + ++stanzasReceived; + lastPacketReceived = SystemClock.elapsedRealtime(); + if (Config.BACKGROUND_STANZA_LOGGING && mXmppConnectionService.checkListeners()) { + Log.d(Config.LOGTAG, "[background stanza] " + element); + } + return element; + } + + private void processIq(final Tag currentTag) throws XmlPullParserException, IOException { + final IqPacket packet = (IqPacket) processPacket(currentTag, PACKET_IQ); + + if (packet.getId() == null) { + return; // an iq packet without id is definitely invalid + } + + if (packet instanceof JinglePacket) { + if (this.jingleListener != null) { + this.jingleListener.onJinglePacketReceived(account, (JinglePacket) packet); + } + } else { + OnIqPacketReceived callback = null; + synchronized (this.packetCallbacks) { + if (packetCallbacks.containsKey(packet.getId())) { + final Pair packetCallbackDuple = packetCallbacks.get(packet.getId()); + // Packets to the server should have responses from the server + if (packetCallbackDuple.first.toServer(account)) { + if (packet.fromServer(account) || mServerIdentity == Identity.FACEBOOK) { + callback = packetCallbackDuple.second; + packetCallbacks.remove(packet.getId()); + } else { + Log.e(Config.LOGTAG, account.getJid().toBareJid().toString() + ": ignoring spoofed iq packet"); + } + } else { + if (packet.getFrom().equals(packetCallbackDuple.first.getTo())) { + callback = packetCallbackDuple.second; + packetCallbacks.remove(packet.getId()); + } else { + Log.e(Config.LOGTAG, account.getJid().toBareJid().toString() + ": ignoring spoofed iq packet"); + } + } + } else if (packet.getType() == IqPacket.TYPE.GET || packet.getType() == IqPacket.TYPE.SET) { + callback = this.unregisteredIqListener; + } + } + if (callback != null) { + callback.onIqPacketReceived(account, packet); + } + } + } + + private void processMessage(final Tag currentTag) throws XmlPullParserException, IOException { + final MessagePacket packet = (MessagePacket) processPacket(currentTag, PACKET_MESSAGE); + this.messageListener.onMessagePacketReceived(account, packet); + } + + private void processPresence(final Tag currentTag) throws XmlPullParserException, IOException { + PresencePacket packet = (PresencePacket) processPacket(currentTag, PACKET_PRESENCE); + this.presenceListener.onPresencePacketReceived(account, packet); + } + + private void sendStartTLS() throws IOException { + final Tag startTLS = Tag.empty("starttls"); + startTLS.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-tls"); + tagWriter.writeTag(startTLS); + } + + + private void switchOverToTls(final Tag currentTag) throws XmlPullParserException, IOException { + tagReader.readTag(); + try { + final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier(); + final InetAddress address = socket == null ? null : socket.getInetAddress(); + + if (address == null) { + throw new IOException("could not setup ssl"); + } + + final SSLSocket sslSocket = (SSLSocket) tlsFactoryVerifier.factory.createSocket(socket, address.getHostAddress(), socket.getPort(), true); + + if (sslSocket == null) { + throw new IOException("could not initialize ssl socket"); + } + + SSLSocketHelper.setSecurity(sslSocket); + + if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), sslSocket.getSession())) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed"); + throw new SecurityException(); + } + tagReader.setInputStream(sslSocket.getInputStream()); + tagWriter.setOutputStream(sslSocket.getOutputStream()); + sendStartStream(); + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS connection established"); + features.encryptionEnabled = true; + final Tag tag = tagReader.readTag(); + if (tag != null && tag.isStart("stream")) { + processStream(); + } else { + throw new IOException("server didn't restart stream after STARTTLS"); + } + sslSocket.close(); + } catch (final NoSuchAlgorithmException | KeyManagementException e1) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed"); + throw new SecurityException(); + } + } + + private void processStreamFeatures(final Tag currentTag) + throws XmlPullParserException, IOException { + this.streamFeatures = tagReader.readElement(currentTag); + if (this.streamFeatures.hasChild("starttls") && !features.encryptionEnabled) { + sendStartTLS(); + } else if (this.streamFeatures.hasChild("register") && account.isOptionSet(Account.OPTION_REGISTER)) { + if (features.encryptionEnabled || Config.ALLOW_NON_TLS_CONNECTIONS) { + sendRegistryRequest(); + } else { + throw new IncompatibleServerException(); + } + } else if (!this.streamFeatures.hasChild("register") + && account.isOptionSet(Account.OPTION_REGISTER)) { + forceCloseSocket(); + changeStatus(Account.State.REGISTRATION_NOT_SUPPORTED); + } 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) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": resuming after stanza #" + stanzasReceived); + } + final ResumePacket resume = new ResumePacket(this.streamId, stanzasReceived, smVersion); + this.tagWriter.writeStanzaAsync(resume); + } else if (needsBinding) { + if (this.streamFeatures.hasChild("bind")) { + sendBindRequest(); + } else { + throw new IncompatibleServerException(); + } + } + } + + private void authenticate() throws IOException { + final List 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()); + } else if (mechanisms.contains("ANONYMOUS")) { + saslMechanism = new Anonymous(tagWriter, account, mXmppConnectionService.getRNG()); + } + if (saslMechanism != null) { final int pinnedMechanism = account.getKeyAsInt(Account.PINNED_MECHANISM_KEY, -1); if (pinnedMechanism > saslMechanism.getPriority()) { Log.e(Config.LOGTAG, "Auth failed. Authentication mechanism " + saslMechanism.getMechanism() + @@ -879,839 +878,840 @@ public class XmppConnection implements Runnable { ") than pinned priority (" + pinnedMechanism + "). Possible downgrade attack?"); throw new SecurityException(); - } - Log.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 extractMechanisms(final Element stream) { - final ArrayList mechanisms = new ArrayList<>(stream - .getChildren().size()); - for (final Element child : stream.getChildren()) { - mechanisms.add(child.getContent()); - } - return mechanisms; - } - - private void sendRegistryRequest() { - final IqPacket register = new IqPacket(IqPacket.TYPE.GET); - register.query("jabber:iq:register"); - register.setTo(account.getServer()); - sendUnmodifiedIqPacket(register, new OnIqPacketReceived() { - - @Override - public void onIqPacketReceived(final Account account, final IqPacket packet) { - boolean failed = false; - if (packet.getType() == IqPacket.TYPE.RESULT - && packet.query().hasChild("username") - && (packet.query().hasChild("password"))) { - final IqPacket register = new IqPacket(IqPacket.TYPE.SET); - final Element username = new Element("username").setContent(account.getUsername()); - final Element password = new Element("password").setContent(account.getPassword()); - register.query("jabber:iq:register").addChild(username); - register.query().addChild(password); - register.setFrom(account.getJid().toBareJid()); - sendUnmodifiedIqPacket(register, registrationResponseListener); - } else if (packet.getType() == IqPacket.TYPE.RESULT - && (packet.query().hasChild("x", "jabber:x:data"))) { - final Data data = Data.parse(packet.query().findChild("x", "jabber:x:data")); - final Element blob = packet.query().findChild("data", "urn:xmpp:bob"); - final String id = packet.getId(); - - Bitmap captcha = null; - if (blob != null) { - try { - final String base64Blob = blob.getContent(); - final byte[] strBlob = Base64.decode(base64Blob, Base64.DEFAULT); - InputStream stream = new ByteArrayInputStream(strBlob); - captcha = BitmapFactory.decodeStream(stream); - } catch (Exception e) { - //ignored - } - } else { - try { - Field url = data.getFieldByName("url"); - String urlString = url.findChildContent("value"); - URL uri = new URL(urlString); - captcha = BitmapFactory.decodeStream(uri.openConnection().getInputStream()); - } catch (IOException e) { - Log.e(Config.LOGTAG, e.toString()); - } - } - - if (captcha != null) { - failed = !mXmppConnectionService.displayCaptchaRequest(account, id, data, captcha); - } - } else { - failed = true; - } - - if (failed) { - final Element instructions = packet.query().findChild("instructions"); - setAccountCreationFailed((instructions != null) ? instructions.getContent() : ""); - } - } - }); - } - - private void setAccountCreationFailed(String instructions) { - changeStatus(Account.State.REGISTRATION_FAILED); - disconnect(true); - Log.d(Config.LOGTAG, account.getJid().toBareJid() - + ": could not register. instructions are" - + instructions); - } - - public void resetEverything() { - resetAttemptCount(); - resetStreamId(); - clearIqCallbacks(); - mStanzaQueue.clear(); - synchronized (this.disco) { - disco.clear(); - } - } - - private void sendBindRequest() { - while(!mXmppConnectionService.areMessagesInitialized() && socket != null && !socket.isClosed()) { - try { - Thread.sleep(500); - } catch (final InterruptedException ignored) { - } - } - needsBinding = false; - clearIqCallbacks(); - final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); - iq.addChild("bind", "urn:ietf:params:xml:ns:xmpp-bind") - .addChild("resource").setContent(account.getResource()); - this.sendUnmodifiedIqPacket(iq, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(final Account account, final IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.TIMEOUT) { - return; - } - final Element bind = packet.findChild("bind"); - if (bind != null && packet.getType() == IqPacket.TYPE.RESULT) { - final Element jid = bind.findChild("jid"); - if (jid != null && jid.getContent() != null) { - try { + } + Log.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 extractMechanisms(final Element stream) { + final ArrayList mechanisms = new ArrayList<>(stream + .getChildren().size()); + for (final Element child : stream.getChildren()) { + mechanisms.add(child.getContent()); + } + return mechanisms; + } + + private void sendRegistryRequest() { + final IqPacket register = new IqPacket(IqPacket.TYPE.GET); + register.query("jabber:iq:register"); + register.setTo(account.getServer()); + sendUnmodifiedIqPacket(register, new OnIqPacketReceived() { + + @Override + public void onIqPacketReceived(final Account account, final IqPacket packet) { + boolean failed = false; + if (packet.getType() == IqPacket.TYPE.RESULT + && packet.query().hasChild("username") + && (packet.query().hasChild("password"))) { + final IqPacket register = new IqPacket(IqPacket.TYPE.SET); + final Element username = new Element("username").setContent(account.getUsername()); + final Element password = new Element("password").setContent(account.getPassword()); + register.query("jabber:iq:register").addChild(username); + register.query().addChild(password); + register.setFrom(account.getJid().toBareJid()); + sendUnmodifiedIqPacket(register, registrationResponseListener); + } else if (packet.getType() == IqPacket.TYPE.RESULT + && (packet.query().hasChild("x", "jabber:x:data"))) { + final Data data = Data.parse(packet.query().findChild("x", "jabber:x:data")); + final Element blob = packet.query().findChild("data", "urn:xmpp:bob"); + final String id = packet.getId(); + + Bitmap captcha = null; + if (blob != null) { + try { + final String base64Blob = blob.getContent(); + final byte[] strBlob = Base64.decode(base64Blob, Base64.DEFAULT); + InputStream stream = new ByteArrayInputStream(strBlob); + captcha = BitmapFactory.decodeStream(stream); + } catch (Exception e) { + //ignored + } + } else { + try { + Field url = data.getFieldByName("url"); + String urlString = url.findChildContent("value"); + URL uri = new URL(urlString); + captcha = BitmapFactory.decodeStream(uri.openConnection().getInputStream()); + } catch (IOException e) { + Log.e(Config.LOGTAG, e.toString()); + } + } + + if (captcha != null) { + failed = !mXmppConnectionService.displayCaptchaRequest(account, id, data, captcha); + } + } else { + failed = true; + } + + if (failed) { + final Element instructions = packet.query().findChild("instructions"); + setAccountCreationFailed((instructions != null) ? instructions.getContent() : ""); + } + } + }); + } + + private void setAccountCreationFailed(String instructions) { + changeStatus(Account.State.REGISTRATION_FAILED); + disconnect(true); + Log.d(Config.LOGTAG, account.getJid().toBareJid() + + ": could not register. instructions are" + + instructions); + } + + public void resetEverything() { + resetAttemptCount(); + resetStreamId(); + clearIqCallbacks(); + mStanzaQueue.clear(); + synchronized (this.disco) { + disco.clear(); + } + } + + private void sendBindRequest() { + while (!mXmppConnectionService.areMessagesInitialized() && socket != null && !socket.isClosed()) { + try { + Thread.sleep(500); + } catch (final InterruptedException ignored) { + } + } + needsBinding = false; + clearIqCallbacks(); + final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); + iq.addChild("bind", "urn:ietf:params:xml:ns:xmpp-bind") + .addChild("resource").setContent(account.getResource()); + this.sendUnmodifiedIqPacket(iq, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(final Account account, final IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.TIMEOUT) { + return; + } + final Element bind = packet.findChild("bind"); + if (bind != null && packet.getType() == IqPacket.TYPE.RESULT) { + final Element jid = bind.findChild("jid"); + if (jid != null && jid.getContent() != null) { + try { if (account.setJid(Jid.fromString(jid.getContent()))) { Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": bare jid changed during bind. updating database"); mXmppConnectionService.databaseBackend.updateAccount(account); } if (streamFeatures.hasChild("session") - && !streamFeatures.findChild("session").hasChild("optional")) { - sendStartSession(); - } else { - sendPostBindInitialization(); - } - return; - } catch (final InvalidJidException e) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": server reported invalid jid ("+jid.getContent()+") on bind"); - } - } else { - Log.d(Config.LOGTAG, account.getJid() + ": disconnecting because of bind failure. (no jid)"); - } - } else { - Log.d(Config.LOGTAG, account.getJid() + ": disconnecting because of bind failure (" + packet.toString()); - } - forceCloseSocket(); - changeStatus(Account.State.BIND_FAILURE); - } - }); - } - - private void clearIqCallbacks() { - final IqPacket failurePacket = new IqPacket(IqPacket.TYPE.TIMEOUT); - final ArrayList callbacks = new ArrayList<>(); - synchronized (this.packetCallbacks) { - if (this.packetCallbacks.size() == 0) { - return; - } - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": clearing "+this.packetCallbacks.size()+" iq callbacks"); - final Iterator> iterator = this.packetCallbacks.values().iterator(); - while (iterator.hasNext()) { - Pair entry = iterator.next(); - callbacks.add(entry.second); - iterator.remove(); - } - } - for(OnIqPacketReceived callback : callbacks) { - callback.onIqPacketReceived(account,failurePacket); - } - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": done clearing iq callbacks. " + this.packetCallbacks.size() + " left"); - } - - public void sendDiscoTimeout() { - if (mWaitForDisco.compareAndSet(true, false)) { - finalizeBind(); - } - } - - private void sendStartSession() { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": sending legacy session to outdated server"); - final IqPacket startSession = new IqPacket(IqPacket.TYPE.SET); - startSession.addChild("session", "urn:ietf:params:xml:ns:xmpp-session"); - this.sendUnmodifiedIqPacket(startSession, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - sendPostBindInitialization(); - } else if (packet.getType() != IqPacket.TYPE.TIMEOUT) { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not init sessions"); - disconnect(true); - } - } - }); - } - - private void sendPostBindInitialization() { - smVersion = 0; - if (streamFeatures.hasChild("sm", "urn:xmpp:sm:3")) { - smVersion = 3; - } else if (streamFeatures.hasChild("sm", "urn:xmpp:sm:2")) { - smVersion = 2; - } - if (smVersion != 0) { - synchronized (this.mStanzaQueue) { - final EnablePacket enable = new EnablePacket(smVersion); - tagWriter.writeStanzaAsync(enable); - stanzasSent = 0; - mStanzaQueue.clear(); - } - } + && !streamFeatures.findChild("session").hasChild("optional")) { + sendStartSession(); + } else { + sendPostBindInitialization(); + } + return; + } catch (final InvalidJidException e) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": server reported invalid jid (" + jid.getContent() + ") on bind"); + } + } else { + Log.d(Config.LOGTAG, account.getJid() + ": disconnecting because of bind failure. (no jid)"); + } + } else { + Log.d(Config.LOGTAG, account.getJid() + ": disconnecting because of bind failure (" + packet.toString()); + } + forceCloseSocket(); + changeStatus(Account.State.BIND_FAILURE); + } + }); + } + + private void clearIqCallbacks() { + final IqPacket failurePacket = new IqPacket(IqPacket.TYPE.TIMEOUT); + final ArrayList callbacks = new ArrayList<>(); + synchronized (this.packetCallbacks) { + if (this.packetCallbacks.size() == 0) { + return; + } + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": clearing " + this.packetCallbacks.size() + " iq callbacks"); + final Iterator> iterator = this.packetCallbacks.values().iterator(); + while (iterator.hasNext()) { + Pair entry = iterator.next(); + callbacks.add(entry.second); + iterator.remove(); + } + } + for (OnIqPacketReceived callback : callbacks) { + callback.onIqPacketReceived(account, failurePacket); + } + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": done clearing iq callbacks. " + this.packetCallbacks.size() + " left"); + } + + public void sendDiscoTimeout() { + if (mWaitForDisco.compareAndSet(true, false)) { + finalizeBind(); + } + } + + private void sendStartSession() { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": sending legacy session to outdated server"); + final IqPacket startSession = new IqPacket(IqPacket.TYPE.SET); + startSession.addChild("session", "urn:ietf:params:xml:ns:xmpp-session"); + this.sendUnmodifiedIqPacket(startSession, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.RESULT) { + sendPostBindInitialization(); + } else if (packet.getType() != IqPacket.TYPE.TIMEOUT) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not init sessions"); + disconnect(true); + } + } + }); + } + + private void sendPostBindInitialization() { + smVersion = 0; + if (streamFeatures.hasChild("sm", "urn:xmpp:sm:3")) { + smVersion = 3; + } else if (streamFeatures.hasChild("sm", "urn:xmpp:sm:2")) { + smVersion = 2; + } + if (smVersion != 0) { + synchronized (this.mStanzaQueue) { + final EnablePacket enable = new EnablePacket(smVersion); + tagWriter.writeStanzaAsync(enable); + stanzasSent = 0; + mStanzaQueue.clear(); + } + } features.carbonsEnabled = false; - features.blockListRequested = false; - synchronized (this.disco) { - this.disco.clear(); - } - mPendingServiceDiscoveries.set(0); + features.blockListRequested = false; + synchronized (this.disco) { + this.disco.clear(); + } + mPendingServiceDiscoveries.set(0); mWaitForDisco.set(mServerIdentity != Identity.NIMBUZZ && smVersion != 0); - lastDiscoStarted = SystemClock.elapsedRealtime(); - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": starting service discovery"); - mXmppConnectionService.scheduleWakeUpCall(Config.CONNECT_DISCO_TIMEOUT, account.getUuid().hashCode()); - Element caps = streamFeatures.findChild("c"); - final String hash = caps == null ? null : caps.getAttribute("hash"); - final String ver = caps == null ? null : caps.getAttribute("ver"); - ServiceDiscoveryResult discoveryResult = null; - if (hash != null && ver != null) { - discoveryResult = mXmppConnectionService.getCachedServiceDiscoveryResult(new Pair<>(hash, ver)); - } - if (discoveryResult == null) { - sendServiceDiscoveryInfo(account.getServer()); - } else { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": server caps came from cache"); - disco.put(account.getServer(), discoveryResult); - } - sendServiceDiscoveryInfo(account.getJid().toBareJid()); - sendServiceDiscoveryItems(account.getServer()); - - if (!mWaitForDisco.get()) { - finalizeBind(); - } - this.lastSessionStarted = SystemClock.elapsedRealtime(); - } - - private void sendServiceDiscoveryInfo(final Jid jid) { - mPendingServiceDiscoveries.incrementAndGet(); - final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); - iq.setTo(jid); - iq.query("http://jabber.org/protocol/disco#info"); - this.sendIqPacket(iq, new OnIqPacketReceived() { - - @Override - public void onIqPacketReceived(final Account account, final IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - boolean advancedStreamFeaturesLoaded; - synchronized (XmppConnection.this.disco) { - ServiceDiscoveryResult result = new ServiceDiscoveryResult(packet); - for (final ServiceDiscoveryResult.Identity id : result.getIdentities()) { - if (mServerIdentity == Identity.UNKNOWN && id.getType().equals("im") && - id.getCategory().equals("server") && id.getName() != null && - jid.equals(account.getServer())) { - switch (id.getName()) { - case "Prosody": - mServerIdentity = Identity.PROSODY; - break; - case "ejabberd": - mServerIdentity = Identity.EJABBERD; - break; - case "Slack-XMPP": - mServerIdentity = Identity.SLACK; - break; - } - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": server name: " + id.getName()); - } - } - if (jid.equals(account.getServer())) { - mXmppConnectionService.databaseBackend.insertDiscoveryResult(result); - } - disco.put(jid, result); - advancedStreamFeaturesLoaded = disco.containsKey(account.getServer()) - && disco.containsKey(account.getJid().toBareJid()); - } - if (advancedStreamFeaturesLoaded && (jid.equals(account.getServer()) || jid.equals(account.getJid().toBareJid()))) { - enableAdvancedStreamFeatures(); - } - } else { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not query disco info for " + jid.toString()); - } - if (packet.getType() != IqPacket.TYPE.TIMEOUT) { - if (mPendingServiceDiscoveries.decrementAndGet() == 0 - && mWaitForDisco.compareAndSet(true, false)) { - finalizeBind(); - } - } - } - }); - } - - private void finalizeBind() { - Log.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(); - } - if (getFeatures().blocking() && !features.blockListRequested) { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": Requesting block list"); - this.sendIqPacket(getIqGenerator().generateGetBlockList(), mXmppConnectionService.getIqParser()); - } - for (final OnAdvancedStreamFeaturesLoaded listener : advancedStreamFeaturesLoadedListeners) { - listener.onAdvancedStreamFeaturesAvailable(account); - } - } - - private void sendServiceDiscoveryItems(final Jid server) { - mPendingServiceDiscoveries.incrementAndGet(); - 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() { - - @Override - public void onIqPacketReceived(final Account account, final IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - final List elements = packet.query().getChildren(); - for (final Element element : elements) { - if (element.getName().equals("item")) { - final Jid jid = element.getAttributeAsJid("jid"); - if (jid != null && !jid.equals(account.getServer())) { - sendServiceDiscoveryInfo(jid); - } - } - } - } else { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not query disco items of " + server); - } - if (packet.getType() != IqPacket.TYPE.TIMEOUT) { - if (mPendingServiceDiscoveries.decrementAndGet() == 0 - && mWaitForDisco.compareAndSet(true, false)) { - finalizeBind(); - } - } - } - }); - } - - private void sendEnableCarbons() { - final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); - iq.addChild("enable", "urn:xmpp:carbons:2"); - this.sendIqPacket(iq, new OnIqPacketReceived() { - - @Override - public void onIqPacketReceived(final Account account, final IqPacket packet) { - if (!packet.hasChild("error")) { - Log.d(Config.LOGTAG, account.getJid().toBareJid() - + ": successfully enabled carbons"); - features.carbonsEnabled = true; - } else { - Log.d(Config.LOGTAG, account.getJid().toBareJid() - + ": error enableing carbons " + packet.toString()); - } - } - }); - } - - private void processStreamError(final Tag currentTag) - throws XmlPullParserException, IOException { - final Element streamError = tagReader.readElement(currentTag); - if (streamError == null) { - return; - } - if (streamError.hasChild("conflict")) { - final String resource = account.getResource().split("\\.")[0]; - account.setResource(resource + "." + nextRandomId()); - Log.d(Config.LOGTAG, - account.getJid().toBareJid() + ": switching resource due to conflict (" - + account.getResource() + ")"); - throw new IOException(); - } else if (streamError.hasChild("host-unknown")) { - throw new StreamErrorHostUnknown(); - } else if (streamError.hasChild("policy-violation")) { - throw new StreamErrorPolicyViolation(); - } else { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": stream error "+streamError.toString()); - throw new StreamError(); - } - } - - private void sendStartStream() throws IOException { - final Tag stream = Tag.start("stream:stream"); - stream.setAttribute("to", account.getServer().toString()); - stream.setAttribute("version", "1.0"); - stream.setAttribute("xml:lang", "en"); - stream.setAttribute("xmlns", "jabber:client"); - stream.setAttribute("xmlns:stream", "http://etherx.jabber.org/streams"); - tagWriter.writeTag(stream); - } - - private String nextRandomId() { - return new BigInteger(50, mXmppConnectionService.getRNG()).toString(32); - } - - public String sendIqPacket(final IqPacket packet, final OnIqPacketReceived callback) { - packet.setFrom(account.getJid()); - return this.sendUnmodifiedIqPacket(packet, callback); - } - - public synchronized String sendUnmodifiedIqPacket(final IqPacket packet, final OnIqPacketReceived callback) { - if (packet.getId() == null) { - final String id = nextRandomId(); - packet.setAttribute("id", id); - } - if (callback != null) { - synchronized (this.packetCallbacks) { - packetCallbacks.put(packet.getId(), new Pair<>(packet, callback)); - } - } - this.sendPacket(packet); - return packet.getId(); - } - - public void sendMessagePacket(final MessagePacket packet) { - this.sendPacket(packet); - } - - public void sendPresencePacket(final PresencePacket packet) { - this.sendPacket(packet); - } - - private synchronized void sendPacket(final AbstractStanza packet) { - if (stanzasSent == Integer.MAX_VALUE) { - resetStreamId(); - disconnect(true); - return; - } - synchronized (this.mStanzaQueue) { - tagWriter.writeStanzaAsync(packet); - if (packet instanceof AbstractAcknowledgeableStanza) { - AbstractAcknowledgeableStanza stanza = (AbstractAcknowledgeableStanza) packet; - ++stanzasSent; - this.mStanzaQueue.append(stanzasSent, stanza); - if (stanza instanceof MessagePacket && stanza.getId() != null && getFeatures().sm()) { - if (Config.EXTENDED_SM_LOGGING) { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": requesting ack for message stanza #" + stanzasSent); - } - tagWriter.writeStanzaAsync(new RequestPacket(this.smVersion)); - } - } - } - } - - public void sendPing() { - if (!r()) { - final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); - iq.setFrom(account.getJid()); - iq.addChild("ping", "urn:xmpp:ping"); - this.sendIqPacket(iq, null); - } - this.lastPingSent = SystemClock.elapsedRealtime(); - } - - public void setOnMessagePacketReceivedListener( - final OnMessagePacketReceived listener) { - this.messageListener = listener; - } - - public void setOnUnregisteredIqPacketReceivedListener( - final OnIqPacketReceived listener) { - this.unregisteredIqListener = listener; - } - - public void setOnPresencePacketReceivedListener( - final OnPresencePacketReceived listener) { - this.presenceListener = listener; - } - - public void setOnJinglePacketReceivedListener( - final OnJinglePacketReceived listener) { - this.jingleListener = listener; - } - - public void setOnStatusChangedListener(final OnStatusChanged listener) { - this.statusListener = listener; - } - - public void setOnBindListener(final OnBindListener listener) { - this.bindListener = listener; - } - - public void setOnMessageAcknowledgeListener(final OnMessageAcknowledged listener) { - this.acknowledgedListener = listener; - } - - public void addOnAdvancedStreamFeaturesAvailableListener(final OnAdvancedStreamFeaturesLoaded listener) { - if (!this.advancedStreamFeaturesLoadedListeners.contains(listener)) { - this.advancedStreamFeaturesLoadedListeners.add(listener); - } - } - - public void waitForPush() { - if (tagWriter.isActive()) { - tagWriter.finish(); - new Thread(new Runnable() { - @Override - public void run() { - try { - while(!tagWriter.finished()) { - Thread.sleep(10); - } - socket.close(); - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": closed tcp without closing stream"); - changeStatus(Account.State.OFFLINE); - } catch (IOException | InterruptedException e) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": error while closing socket for waitForPush()"); - } - } - }).start(); - } else { - forceCloseSocket(); - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": closed tcp without closing stream (no waiting)"); - } - } - - private void forceCloseSocket() { - if (socket != null) { - try { - socket.close(); - } catch (IOException e) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": io exception "+e.getMessage()+" during force close"); - } - } else { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": socket was null during force close"); - } - } - - public void interrupt() { - Thread.currentThread().interrupt(); - } - - public void disconnect(final boolean force) { - interrupt(); - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": disconnecting force="+Boolean.valueOf(force)); - if (force) { - tagWriter.forceClose(); - forceCloseSocket(); - } else { - if (tagWriter.isActive()) { - tagWriter.finish(); - try { - int i = 0; - boolean warned = false; - while (!tagWriter.finished() && socket.isConnected() && i <= 10) { - if (!warned) { - Log.d(Config.LOGTAG, account.getJid().toBareJid()+": waiting for tag writer to finish"); - warned = true; - } - try { - Thread.sleep(200); - } catch(InterruptedException e) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": sleep interrupted"); - } - i++; - } - if (warned) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": tag writer has finished"); - } - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": closing stream"); - tagWriter.writeTag(Tag.end("stream:stream")); - } catch (final IOException e) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": io exception during disconnect ("+e.getMessage()+")"); - } finally { - forceCloseSocket(); - } - } - } - } - - public void resetStreamId() { - this.streamId = null; - } - - private List> findDiscoItemsByFeature(final String feature) { - synchronized (this.disco) { - final List> items = new ArrayList<>(); - for (final Entry cursor : this.disco.entrySet()) { - if (cursor.getValue().getFeatures().contains(feature)) { - items.add(cursor); - } - } - return items; - } - } - - public Jid findDiscoItemByFeature(final String feature) { - final List> items = findDiscoItemsByFeature(feature); - if (items.size() >= 1) { - return items.get(0).getKey(); - } - return null; - } - - public boolean r() { - if (getFeatures().sm()) { - this.tagWriter.writeStanzaAsync(new RequestPacket(smVersion)); - return true; - } else { - return false; - } - } - - public String getMucServer() { - synchronized (this.disco) { - for (final Entry cursor : disco.entrySet()) { - final ServiceDiscoveryResult value = cursor.getValue(); - if (value.getFeatures().contains("http://jabber.org/protocol/muc") - && !value.getFeatures().contains("jabber:iq:gateway") - && !value.hasIdentity("conference", "irc")) { - return cursor.getKey().toString(); - } - } - } - return null; - } - - public int getTimeToNextAttempt() { - final int interval = Math.min((int) (25 * Math.pow(1.3, attempt)), 300); - final int secondsSinceLast = (int) ((SystemClock.elapsedRealtime() - this.lastConnect) / 1000); - return interval - secondsSinceLast; - } - - public int getAttempt() { - return this.attempt; - } - - public Features getFeatures() { - return this.features; - } - - public long getLastSessionEstablished() { - final long diff = SystemClock.elapsedRealtime() - this.lastSessionStarted; - return System.currentTimeMillis() - diff; - } - - public long getLastConnect() { - return this.lastConnect; - } - - public long getLastPingSent() { - return this.lastPingSent; - } - - public long getLastDiscoStarted() { - return this.lastDiscoStarted; - } - public long getLastPacketReceived() { - return this.lastPacketReceived; - } - - public void sendActive() { - this.sendPacket(new ActivePacket()); - } - - public void sendInactive() { - this.sendPacket(new InactivePacket()); - } - - public void resetAttemptCount() { - this.attempt = 0; - this.lastConnect = 0; - } - - public void setInteractive(boolean interactive) { - this.mInteractive = interactive; - } - - public Identity getServerIdentity() { - return mServerIdentity; - } - - private class UnauthorizedException extends IOException { - - } - - private class SecurityException extends IOException { - - } - - private class IncompatibleServerException extends IOException { - - } - - private class StreamErrorHostUnknown extends StreamError { - - } - - private class StreamErrorPolicyViolation extends StreamError { - - } + lastDiscoStarted = SystemClock.elapsedRealtime(); + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": starting service discovery"); + mXmppConnectionService.scheduleWakeUpCall(Config.CONNECT_DISCO_TIMEOUT, account.getUuid().hashCode()); + Element caps = streamFeatures.findChild("c"); + final String hash = caps == null ? null : caps.getAttribute("hash"); + final String ver = caps == null ? null : caps.getAttribute("ver"); + ServiceDiscoveryResult discoveryResult = null; + if (hash != null && ver != null) { + discoveryResult = mXmppConnectionService.getCachedServiceDiscoveryResult(new Pair<>(hash, ver)); + } + if (discoveryResult == null) { + sendServiceDiscoveryInfo(account.getServer()); + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": server caps came from cache"); + disco.put(account.getServer(), discoveryResult); + } + sendServiceDiscoveryInfo(account.getJid().toBareJid()); + sendServiceDiscoveryItems(account.getServer()); - private class StreamError extends IOException { + if (!mWaitForDisco.get()) { + finalizeBind(); + } + this.lastSessionStarted = SystemClock.elapsedRealtime(); + } + + private void sendServiceDiscoveryInfo(final Jid jid) { + mPendingServiceDiscoveries.incrementAndGet(); + final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); + iq.setTo(jid); + iq.query("http://jabber.org/protocol/disco#info"); + this.sendIqPacket(iq, new OnIqPacketReceived() { + + @Override + public void onIqPacketReceived(final Account account, final IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.RESULT) { + boolean advancedStreamFeaturesLoaded; + synchronized (XmppConnection.this.disco) { + ServiceDiscoveryResult result = new ServiceDiscoveryResult(packet); + for (final ServiceDiscoveryResult.Identity id : result.getIdentities()) { + if (mServerIdentity == Identity.UNKNOWN && id.getType().equals("im") && + id.getCategory().equals("server") && id.getName() != null && + jid.equals(account.getServer())) { + switch (id.getName()) { + case "Prosody": + mServerIdentity = Identity.PROSODY; + break; + case "ejabberd": + mServerIdentity = Identity.EJABBERD; + break; + case "Slack-XMPP": + mServerIdentity = Identity.SLACK; + break; + } + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": server name: " + id.getName()); + } + } + if (jid.equals(account.getServer())) { + mXmppConnectionService.databaseBackend.insertDiscoveryResult(result); + } + disco.put(jid, result); + advancedStreamFeaturesLoaded = disco.containsKey(account.getServer()) + && disco.containsKey(account.getJid().toBareJid()); + } + if (advancedStreamFeaturesLoaded && (jid.equals(account.getServer()) || jid.equals(account.getJid().toBareJid()))) { + enableAdvancedStreamFeatures(); + } + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not query disco info for " + jid.toString()); + } + if (packet.getType() != IqPacket.TYPE.TIMEOUT) { + if (mPendingServiceDiscoveries.decrementAndGet() == 0 + && mWaitForDisco.compareAndSet(true, false)) { + finalizeBind(); + } + } + } + }); + } + + private void finalizeBind() { + Log.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(); + } + if (getFeatures().blocking() && !features.blockListRequested) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": Requesting block list"); + this.sendIqPacket(getIqGenerator().generateGetBlockList(), mXmppConnectionService.getIqParser()); + } + for (final OnAdvancedStreamFeaturesLoaded listener : advancedStreamFeaturesLoadedListeners) { + listener.onAdvancedStreamFeaturesAvailable(account); + } + } + + private void sendServiceDiscoveryItems(final Jid server) { + mPendingServiceDiscoveries.incrementAndGet(); + 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() { + + @Override + public void onIqPacketReceived(final Account account, final IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.RESULT) { + final List elements = packet.query().getChildren(); + for (final Element element : elements) { + if (element.getName().equals("item")) { + final Jid jid = element.getAttributeAsJid("jid"); + if (jid != null && !jid.equals(account.getServer())) { + sendServiceDiscoveryInfo(jid); + } + } + } + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not query disco items of " + server); + } + if (packet.getType() != IqPacket.TYPE.TIMEOUT) { + if (mPendingServiceDiscoveries.decrementAndGet() == 0 + && mWaitForDisco.compareAndSet(true, false)) { + finalizeBind(); + } + } + } + }); + } + + private void sendEnableCarbons() { + final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); + iq.addChild("enable", "urn:xmpp:carbons:2"); + this.sendIqPacket(iq, new OnIqPacketReceived() { + + @Override + public void onIqPacketReceived(final Account account, final IqPacket packet) { + if (!packet.hasChild("error")) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + + ": successfully enabled carbons"); + features.carbonsEnabled = true; + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + + ": error enableing carbons " + packet.toString()); + } + } + }); + } + + private void processStreamError(final Tag currentTag) + throws XmlPullParserException, IOException { + final Element streamError = tagReader.readElement(currentTag); + if (streamError == null) { + return; + } + if (streamError.hasChild("conflict")) { + final String resource = account.getResource().split("\\.")[0]; + account.setResource(resource + "." + nextRandomId()); + Log.d(Config.LOGTAG, + account.getJid().toBareJid() + ": switching resource due to conflict (" + + account.getResource() + ")"); + throw new IOException(); + } else if (streamError.hasChild("host-unknown")) { + throw new StreamErrorHostUnknown(); + } else if (streamError.hasChild("policy-violation")) { + throw new StreamErrorPolicyViolation(); + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": stream error " + streamError.toString()); + throw new StreamError(); + } + } + + private void sendStartStream() throws IOException { + final Tag stream = Tag.start("stream:stream"); + stream.setAttribute("to", account.getServer().toString()); + stream.setAttribute("version", "1.0"); + stream.setAttribute("xml:lang", "en"); + stream.setAttribute("xmlns", "jabber:client"); + stream.setAttribute("xmlns:stream", "http://etherx.jabber.org/streams"); + tagWriter.writeTag(stream); + } + + private String nextRandomId() { + return new BigInteger(50, mXmppConnectionService.getRNG()).toString(32); + } + + public String sendIqPacket(final IqPacket packet, final OnIqPacketReceived callback) { + packet.setFrom(account.getJid()); + return this.sendUnmodifiedIqPacket(packet, callback); + } + + public synchronized String sendUnmodifiedIqPacket(final IqPacket packet, final OnIqPacketReceived callback) { + if (packet.getId() == null) { + final String id = nextRandomId(); + packet.setAttribute("id", id); + } + if (callback != null) { + synchronized (this.packetCallbacks) { + packetCallbacks.put(packet.getId(), new Pair<>(packet, callback)); + } + } + this.sendPacket(packet); + return packet.getId(); + } + + public void sendMessagePacket(final MessagePacket packet) { + this.sendPacket(packet); + } + + public void sendPresencePacket(final PresencePacket packet) { + this.sendPacket(packet); + } + + private synchronized void sendPacket(final AbstractStanza packet) { + if (stanzasSent == Integer.MAX_VALUE) { + resetStreamId(); + disconnect(true); + return; + } + synchronized (this.mStanzaQueue) { + tagWriter.writeStanzaAsync(packet); + if (packet instanceof AbstractAcknowledgeableStanza) { + AbstractAcknowledgeableStanza stanza = (AbstractAcknowledgeableStanza) packet; + ++stanzasSent; + this.mStanzaQueue.append(stanzasSent, stanza); + if (stanza instanceof MessagePacket && stanza.getId() != null && getFeatures().sm()) { + if (Config.EXTENDED_SM_LOGGING) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": requesting ack for message stanza #" + stanzasSent); + } + tagWriter.writeStanzaAsync(new RequestPacket(this.smVersion)); + } + } + } + } + + public void sendPing() { + if (!r()) { + final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); + iq.setFrom(account.getJid()); + iq.addChild("ping", "urn:xmpp:ping"); + this.sendIqPacket(iq, null); + } + this.lastPingSent = SystemClock.elapsedRealtime(); + } + + public void setOnMessagePacketReceivedListener( + final OnMessagePacketReceived listener) { + this.messageListener = listener; + } + + public void setOnUnregisteredIqPacketReceivedListener( + final OnIqPacketReceived listener) { + this.unregisteredIqListener = listener; + } + + public void setOnPresencePacketReceivedListener( + final OnPresencePacketReceived listener) { + this.presenceListener = listener; + } + + public void setOnJinglePacketReceivedListener( + final OnJinglePacketReceived listener) { + this.jingleListener = listener; + } + + public void setOnStatusChangedListener(final OnStatusChanged listener) { + this.statusListener = listener; + } + + public void setOnBindListener(final OnBindListener listener) { + this.bindListener = listener; + } + + public void setOnMessageAcknowledgeListener(final OnMessageAcknowledged listener) { + this.acknowledgedListener = listener; + } + + public void addOnAdvancedStreamFeaturesAvailableListener(final OnAdvancedStreamFeaturesLoaded listener) { + if (!this.advancedStreamFeaturesLoadedListeners.contains(listener)) { + this.advancedStreamFeaturesLoadedListeners.add(listener); + } + } + + public void waitForPush() { + if (tagWriter.isActive()) { + tagWriter.finish(); + new Thread(new Runnable() { + @Override + public void run() { + try { + while (!tagWriter.finished()) { + Thread.sleep(10); + } + socket.close(); + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": closed tcp without closing stream"); + changeStatus(Account.State.OFFLINE); + } catch (IOException | InterruptedException e) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": error while closing socket for waitForPush()"); + } + } + }).start(); + } else { + forceCloseSocket(); + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": closed tcp without closing stream (no waiting)"); + } + } + + private void forceCloseSocket() { + if (socket != null) { + try { + socket.close(); + } catch (IOException e) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": io exception " + e.getMessage() + " during force close"); + } + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": socket was null during force close"); + } + } + + public void interrupt() { + Thread.currentThread().interrupt(); + } + + public void disconnect(final boolean force) { + interrupt(); + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": disconnecting force=" + Boolean.valueOf(force)); + if (force) { + tagWriter.forceClose(); + forceCloseSocket(); + } else { + if (tagWriter.isActive()) { + tagWriter.finish(); + try { + int i = 0; + boolean warned = false; + while (!tagWriter.finished() && socket.isConnected() && i <= 10) { + if (!warned) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": waiting for tag writer to finish"); + warned = true; + } + try { + Thread.sleep(200); + } catch (InterruptedException e) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": sleep interrupted"); + } + i++; + } + if (warned) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": tag writer has finished"); + } + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": closing stream"); + tagWriter.writeTag(Tag.end("stream:stream")); + } catch (final IOException e) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": io exception during disconnect (" + e.getMessage() + ")"); + } finally { + forceCloseSocket(); + } + } + } + } + + public void resetStreamId() { + this.streamId = null; + } + + private List> findDiscoItemsByFeature(final String feature) { + synchronized (this.disco) { + final List> items = new ArrayList<>(); + for (final Entry cursor : this.disco.entrySet()) { + if (cursor.getValue().getFeatures().contains(feature)) { + items.add(cursor); + } + } + return items; + } + } + + public Jid findDiscoItemByFeature(final String feature) { + final List> items = findDiscoItemsByFeature(feature); + if (items.size() >= 1) { + return items.get(0).getKey(); + } + return null; + } + + public boolean r() { + if (getFeatures().sm()) { + this.tagWriter.writeStanzaAsync(new RequestPacket(smVersion)); + return true; + } else { + return false; + } + } + + public String getMucServer() { + synchronized (this.disco) { + for (final Entry cursor : disco.entrySet()) { + final ServiceDiscoveryResult value = cursor.getValue(); + if (value.getFeatures().contains("http://jabber.org/protocol/muc") + && !value.getFeatures().contains("jabber:iq:gateway") + && !value.hasIdentity("conference", "irc")) { + return cursor.getKey().toString(); + } + } + } + return null; + } + + public int getTimeToNextAttempt() { + final int interval = Math.min((int) (25 * Math.pow(1.3, attempt)), 300); + final int secondsSinceLast = (int) ((SystemClock.elapsedRealtime() - this.lastConnect) / 1000); + return interval - secondsSinceLast; + } + + public int getAttempt() { + return this.attempt; + } + + public Features getFeatures() { + return this.features; + } + + public long getLastSessionEstablished() { + final long diff = SystemClock.elapsedRealtime() - this.lastSessionStarted; + return System.currentTimeMillis() - diff; + } + + public long getLastConnect() { + return this.lastConnect; + } + + public long getLastPingSent() { + return this.lastPingSent; + } + + public long getLastDiscoStarted() { + return this.lastDiscoStarted; + } + + public long getLastPacketReceived() { + return this.lastPacketReceived; + } + + public void sendActive() { + this.sendPacket(new ActivePacket()); + } - } + public void sendInactive() { + this.sendPacket(new InactivePacket()); + } - private class PaymentRequiredException extends IOException { + public void resetAttemptCount() { + this.attempt = 0; + this.lastConnect = 0; + } - } + public void setInteractive(boolean interactive) { + this.mInteractive = interactive; + } - public enum Identity { - FACEBOOK, - SLACK, - EJABBERD, - PROSODY, - NIMBUZZ, - UNKNOWN - } + public Identity getServerIdentity() { + return mServerIdentity; + } - public class Features { - XmppConnection connection; - private boolean carbonsEnabled = false; - private boolean encryptionEnabled = false; - private boolean blockListRequested = false; + private class UnauthorizedException extends IOException { - public Features(final XmppConnection connection) { - this.connection = connection; - } + } - private boolean hasDiscoFeature(final Jid server, final String feature) { - synchronized (XmppConnection.this.disco) { - return connection.disco.containsKey(server) && - connection.disco.get(server).getFeatures().contains(feature); - } - } + private class SecurityException extends IOException { - public boolean carbons() { - return hasDiscoFeature(account.getServer(), "urn:xmpp:carbons:2"); - } + } - public boolean blocking() { - return hasDiscoFeature(account.getServer(), Xmlns.BLOCKING); - } + private class IncompatibleServerException extends IOException { + + } + + private class StreamErrorHostUnknown extends StreamError { + + } + + private class StreamErrorPolicyViolation extends StreamError { + + } + + private class StreamError extends IOException { + + } + + private class PaymentRequiredException extends IOException { + + } + + public enum Identity { + FACEBOOK, + SLACK, + EJABBERD, + PROSODY, + NIMBUZZ, + UNKNOWN + } + + public class Features { + XmppConnection connection; + private boolean carbonsEnabled = false; + private boolean encryptionEnabled = false; + private boolean blockListRequested = false; + + public Features(final XmppConnection connection) { + this.connection = connection; + } + + private boolean hasDiscoFeature(final Jid server, final String feature) { + synchronized (XmppConnection.this.disco) { + return connection.disco.containsKey(server) && + connection.disco.get(server).getFeatures().contains(feature); + } + } + + public boolean carbons() { + return hasDiscoFeature(account.getServer(), "urn:xmpp:carbons:2"); + } + + public boolean blocking() { + return hasDiscoFeature(account.getServer(), Xmlns.BLOCKING); + } public boolean spamReporting() { return hasDiscoFeature(account.getServer(), "urn:xmpp:reporting:reason:spam:0"); } - public boolean register() { - return hasDiscoFeature(account.getServer(), Xmlns.REGISTER); - } - - public boolean sm() { - return streamId != null - || (connection.streamFeatures != null && connection.streamFeatures.hasChild("sm")); - } - - public boolean csi() { - return connection.streamFeatures != null && connection.streamFeatures.hasChild("csi", "urn:xmpp:csi:0"); - } - - public boolean pep() { - synchronized (XmppConnection.this.disco) { - 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() { - return hasDiscoFeature(account.getJid().toBareJid(), "urn:xmpp:mam:0") - || hasDiscoFeature(account.getServer(), "urn:xmpp:mam:0"); - } - - public boolean push() { - return hasDiscoFeature(account.getJid().toBareJid(), "urn:xmpp:push:0") - || hasDiscoFeature(account.getServer(), "urn:xmpp:push:0"); - } - - public boolean rosterVersioning() { - return connection.streamFeatures != null && connection.streamFeatures.hasChild("ver"); - } - - public void setBlockListRequested(boolean value) { - this.blockListRequested = value; - } - - public boolean httpUpload(long filesize) { - if (Config.DISABLE_HTTP_UPLOAD) { - return false; - } else { - List> items = findDiscoItemsByFeature(Xmlns.HTTP_UPLOAD); - if (items.size() > 0) { - try { - long maxsize = Long.parseLong(items.get(0).getValue().getExtendedDiscoInformation(Xmlns.HTTP_UPLOAD, "max-file-size")); - if(filesize <= maxsize) { - return true; - } else { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": http upload is not available for files with size "+filesize+" (max is "+maxsize+")"); - return false; - } - } catch (Exception e) { - return true; - } - } else { - return false; - } - } - } - - public long getMaxHttpUploadSize() { - List> items = findDiscoItemsByFeature(Xmlns.HTTP_UPLOAD); - if (items.size() > 0) { - try { - return Long.parseLong(items.get(0).getValue().getExtendedDiscoInformation(Xmlns.HTTP_UPLOAD, "max-file-size")); - } catch (Exception e) { - return -1; - } - } else { - return -1; - } - } + public boolean register() { + return hasDiscoFeature(account.getServer(), Xmlns.REGISTER); + } + + public boolean sm() { + return streamId != null + || (connection.streamFeatures != null && connection.streamFeatures.hasChild("sm")); + } + + public boolean csi() { + return connection.streamFeatures != null && connection.streamFeatures.hasChild("csi", "urn:xmpp:csi:0"); + } + + public boolean pep() { + synchronized (XmppConnection.this.disco) { + 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() { + return hasDiscoFeature(account.getJid().toBareJid(), "urn:xmpp:mam:0") + || hasDiscoFeature(account.getServer(), "urn:xmpp:mam:0"); + } + + public boolean push() { + return hasDiscoFeature(account.getJid().toBareJid(), "urn:xmpp:push:0") + || hasDiscoFeature(account.getServer(), "urn:xmpp:push:0"); + } + + public boolean rosterVersioning() { + return connection.streamFeatures != null && connection.streamFeatures.hasChild("ver"); + } + + public void setBlockListRequested(boolean value) { + this.blockListRequested = value; + } + + public boolean httpUpload(long filesize) { + if (Config.DISABLE_HTTP_UPLOAD) { + return false; + } else { + List> items = findDiscoItemsByFeature(Xmlns.HTTP_UPLOAD); + if (items.size() > 0) { + try { + long maxsize = Long.parseLong(items.get(0).getValue().getExtendedDiscoInformation(Xmlns.HTTP_UPLOAD, "max-file-size")); + if (filesize <= maxsize) { + return true; + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": http upload is not available for files with size " + filesize + " (max is " + maxsize + ")"); + return false; + } + } catch (Exception e) { + return true; + } + } else { + return false; + } + } + } + + public long getMaxHttpUploadSize() { + List> items = findDiscoItemsByFeature(Xmlns.HTTP_UPLOAD); + if (items.size() > 0) { + try { + return Long.parseLong(items.get(0).getValue().getExtendedDiscoInformation(Xmlns.HTTP_UPLOAD, "max-file-size")); + } catch (Exception e) { + return -1; + } + } else { + return -1; + } + } public boolean stanzaIds() { - return hasDiscoFeature(account.getJid().toBareJid(),Xmlns.STANZA_IDS); + return hasDiscoFeature(account.getJid().toBareJid(), Xmlns.STANZA_IDS); } - } + } - private IqGenerator getIqGenerator() { - return mXmppConnectionService.getIqGenerator(); - } + private IqGenerator getIqGenerator() { + return mXmppConnectionService.getIqGenerator(); + } } diff --git a/src/main/java/de/pixart/messenger/xmpp/chatstate/ChatState.java b/src/main/java/de/pixart/messenger/xmpp/chatstate/ChatState.java index c3117455c..169d0bb66 100644 --- a/src/main/java/de/pixart/messenger/xmpp/chatstate/ChatState.java +++ b/src/main/java/de/pixart/messenger/xmpp/chatstate/ChatState.java @@ -4,29 +4,29 @@ import de.pixart.messenger.xml.Element; public enum ChatState { - ACTIVE, INACTIVE, GONE, COMPOSING, PAUSED; + ACTIVE, INACTIVE, GONE, COMPOSING, PAUSED; - public static ChatState parse(Element element) { - final String NAMESPACE = "http://jabber.org/protocol/chatstates"; - if (element.hasChild("active",NAMESPACE)) { - return ACTIVE; - } else if (element.hasChild("inactive",NAMESPACE)) { - return INACTIVE; - } else if (element.hasChild("composing",NAMESPACE)) { - return COMPOSING; - } else if (element.hasChild("gone",NAMESPACE)) { - return GONE; - } else if (element.hasChild("paused",NAMESPACE)) { - return PAUSED; - } else { - return null; - } - } + public static ChatState parse(Element element) { + final String NAMESPACE = "http://jabber.org/protocol/chatstates"; + if (element.hasChild("active", NAMESPACE)) { + return ACTIVE; + } else if (element.hasChild("inactive", NAMESPACE)) { + return INACTIVE; + } else if (element.hasChild("composing", NAMESPACE)) { + return COMPOSING; + } else if (element.hasChild("gone", NAMESPACE)) { + return GONE; + } else if (element.hasChild("paused", NAMESPACE)) { + return PAUSED; + } else { + return null; + } + } - public static Element toElement(ChatState state) { - final String NAMESPACE = "http://jabber.org/protocol/chatstates"; - final Element element = new Element(state.toString().toLowerCase()); - element.setAttribute("xmlns",NAMESPACE); - return element; - } + public static Element toElement(ChatState state) { + final String NAMESPACE = "http://jabber.org/protocol/chatstates"; + final Element element = new Element(state.toString().toLowerCase()); + element.setAttribute("xmlns", NAMESPACE); + return element; + } } diff --git a/src/main/java/de/pixart/messenger/xmpp/forms/Data.java b/src/main/java/de/pixart/messenger/xmpp/forms/Data.java index 8ae70f9c7..f92e468e5 100644 --- a/src/main/java/de/pixart/messenger/xmpp/forms/Data.java +++ b/src/main/java/de/pixart/messenger/xmpp/forms/Data.java @@ -9,91 +9,91 @@ import de.pixart.messenger.xml.Element; public class Data extends Element { - public static final String FORM_TYPE = "FORM_TYPE"; - - public Data() { - super("x"); - this.setAttribute("xmlns","jabber:x:data"); - } - - public List getFields() { - ArrayList fields = new ArrayList(); - for(Element child : getChildren()) { - if (child.getName().equals("field") - && !FORM_TYPE.equals(child.getAttribute("var"))) { - fields.add(Field.parse(child)); - } - } - return fields; - } - - public Field getFieldByName(String needle) { - for(Element child : getChildren()) { - if (child.getName().equals("field") - && needle.equals(child.getAttribute("var"))) { - return Field.parse(child); - } - } - return null; - } - - public void put(String name, String value) { - Field field = getFieldByName(name); - if (field == null) { - field = new Field(name); - this.addChild(field); - } - field.setValue(value); - } - - public void put(String name, Collection values) { - Field field = getFieldByName(name); - if (field == null) { - field = new Field(name); - this.addChild(field); - } - field.setValues(values); - } - - public void submit() { - this.setAttribute("type","submit"); - removeUnnecessaryChildren(); - for(Field field : getFields()) { - field.removeNonValueChildren(); - } - } - - private void removeUnnecessaryChildren() { - for(Iterator iterator = this.children.iterator(); iterator.hasNext();) { - Element element = iterator.next(); - if (!element.getName().equals("field") && !element.getName().equals("title")) { - iterator.remove(); - } - } - } - - public static Data parse(Element element) { - Data data = new Data(); - data.setAttributes(element.getAttributes()); - data.setChildren(element.getChildren()); - return data; - } - - public void setFormType(String formType) { - this.put(FORM_TYPE, formType); - } - - public String getFormType() { - String type = getValue(FORM_TYPE); - return type == null ? "" : type; - } - - public String getValue(String name) { - Field field = this.getFieldByName(name); - return field == null ? null : field.getValue(); - } - - public String getTitle() { - return findChildContent("title"); - } + public static final String FORM_TYPE = "FORM_TYPE"; + + public Data() { + super("x"); + this.setAttribute("xmlns", "jabber:x:data"); + } + + public List getFields() { + ArrayList fields = new ArrayList(); + for (Element child : getChildren()) { + if (child.getName().equals("field") + && !FORM_TYPE.equals(child.getAttribute("var"))) { + fields.add(Field.parse(child)); + } + } + return fields; + } + + public Field getFieldByName(String needle) { + for (Element child : getChildren()) { + if (child.getName().equals("field") + && needle.equals(child.getAttribute("var"))) { + return Field.parse(child); + } + } + return null; + } + + public void put(String name, String value) { + Field field = getFieldByName(name); + if (field == null) { + field = new Field(name); + this.addChild(field); + } + field.setValue(value); + } + + public void put(String name, Collection values) { + Field field = getFieldByName(name); + if (field == null) { + field = new Field(name); + this.addChild(field); + } + field.setValues(values); + } + + public void submit() { + this.setAttribute("type", "submit"); + removeUnnecessaryChildren(); + for (Field field : getFields()) { + field.removeNonValueChildren(); + } + } + + private void removeUnnecessaryChildren() { + for (Iterator iterator = this.children.iterator(); iterator.hasNext(); ) { + Element element = iterator.next(); + if (!element.getName().equals("field") && !element.getName().equals("title")) { + iterator.remove(); + } + } + } + + public static Data parse(Element element) { + Data data = new Data(); + data.setAttributes(element.getAttributes()); + data.setChildren(element.getChildren()); + return data; + } + + public void setFormType(String formType) { + this.put(FORM_TYPE, formType); + } + + public String getFormType() { + String type = getValue(FORM_TYPE); + return type == null ? "" : type; + } + + public String getValue(String name) { + Field field = this.getFieldByName(name); + return field == null ? null : field.getValue(); + } + + public String getTitle() { + return findChildContent("title"); + } } diff --git a/src/main/java/de/pixart/messenger/xmpp/forms/Field.java b/src/main/java/de/pixart/messenger/xmpp/forms/Field.java index 4cf5fc6b7..a05f9538e 100644 --- a/src/main/java/de/pixart/messenger/xmpp/forms/Field.java +++ b/src/main/java/de/pixart/messenger/xmpp/forms/Field.java @@ -9,73 +9,73 @@ import de.pixart.messenger.xml.Element; public class Field extends Element { - public Field(String name) { - super("field"); - this.setAttribute("var",name); - } + public Field(String name) { + super("field"); + this.setAttribute("var", name); + } - private Field() { - super("field"); - } + private Field() { + super("field"); + } - public String getFieldName() { - return this.getAttribute("var"); - } + public String getFieldName() { + return this.getAttribute("var"); + } - public void setValue(String value) { - this.children.clear(); - this.addChild("value").setContent(value); - } + public void setValue(String value) { + this.children.clear(); + this.addChild("value").setContent(value); + } - public void setValues(Collection values) { - this.children.clear(); - for(String value : values) { - this.addChild("value").setContent(value); - } - } + public void setValues(Collection values) { + this.children.clear(); + for (String value : values) { + this.addChild("value").setContent(value); + } + } - public void removeNonValueChildren() { - for(Iterator iterator = this.children.iterator(); iterator.hasNext();) { - Element element = iterator.next(); - if (!element.getName().equals("value")) { - iterator.remove(); - } - } - } + public void removeNonValueChildren() { + for (Iterator iterator = this.children.iterator(); iterator.hasNext(); ) { + Element element = iterator.next(); + if (!element.getName().equals("value")) { + iterator.remove(); + } + } + } - public static Field parse(Element element) { - Field field = new Field(); - field.setAttributes(element.getAttributes()); - field.setChildren(element.getChildren()); - return field; - } + public static Field parse(Element element) { + Field field = new Field(); + field.setAttributes(element.getAttributes()); + field.setChildren(element.getChildren()); + return field; + } - public String getValue() { - return findChildContent("value"); - } + public String getValue() { + return findChildContent("value"); + } - public List getValues() { - List values = new ArrayList<>(); - for(Element child : getChildren()) { - if ("value".equals(child.getName())) { - String content = child.getContent(); - if (content != null) { - values.add(content); - } - } - } - return values; - } + public List getValues() { + List values = new ArrayList<>(); + for (Element child : getChildren()) { + if ("value".equals(child.getName())) { + String content = child.getContent(); + if (content != null) { + values.add(content); + } + } + } + return values; + } - public String getLabel() { - return getAttribute("label"); - } + public String getLabel() { + return getAttribute("label"); + } - public String getType() { - return getAttribute("type"); - } + public String getType() { + return getAttribute("type"); + } - public boolean isRequired() { - return hasChild("required"); - } + public boolean isRequired() { + return hasChild("required"); + } } diff --git a/src/main/java/de/pixart/messenger/xmpp/jid/InvalidJidException.java b/src/main/java/de/pixart/messenger/xmpp/jid/InvalidJidException.java index 9f091063a..fa1b396d4 100644 --- a/src/main/java/de/pixart/messenger/xmpp/jid/InvalidJidException.java +++ b/src/main/java/de/pixart/messenger/xmpp/jid/InvalidJidException.java @@ -8,7 +8,7 @@ public class InvalidJidException extends Exception { public final static String INVALID_PART_LENGTH = "JID part must be between 0 and 1023 characters"; public final static String INVALID_CHARACTER = "JID contains an invalid character"; public final static String STRINGPREP_FAIL = "The STRINGPREP operation has failed for the given JID"; - public final static String IS_NULL = "JID can not be NULL"; + public final static String IS_NULL = "JID can not be NULL"; /** * Constructs a new {@code Exception} that includes the current stack trace. @@ -31,7 +31,7 @@ public class InvalidJidException extends Exception { * specified detail message and the specified cause. * * @param detailMessage the detail message for this exception. - * @param throwable the cause of this exception. + * @param throwable the cause of this exception. */ public InvalidJidException(final String detailMessage, final Throwable throwable) { super(detailMessage, throwable); diff --git a/src/main/java/de/pixart/messenger/xmpp/jid/Jid.java b/src/main/java/de/pixart/messenger/xmpp/jid/Jid.java index c596367ca..123e6ee8f 100644 --- a/src/main/java/de/pixart/messenger/xmpp/jid/Jid.java +++ b/src/main/java/de/pixart/messenger/xmpp/jid/Jid.java @@ -15,225 +15,225 @@ import gnu.inet.encoding.StringprepException; */ public final class Jid { - private static LruCache cache = new LruCache<>(1024); + private static LruCache cache = new LruCache<>(1024); - private final String localpart; - private final String domainpart; - private final String resourcepart; + private final String localpart; + private final String domainpart; + private final String resourcepart; // It's much more efficient to store the full JID as well as the parts instead of figuring them // all out every time (since some characters are displayed but aren't used for comparisons). private final String displayjid; - public String getLocalpart() { - return localpart; - } - - public String getDomainpart() { - return IDN.toUnicode(domainpart); - } - - public String getResourcepart() { - return resourcepart; - } - - public static Jid fromSessionID(final SessionID id) throws InvalidJidException{ - if (id.getUserID().isEmpty()) { - return Jid.fromString(id.getAccountID()); - } else { - return Jid.fromString(id.getAccountID()+"/"+id.getUserID()); - } - } - - public static Jid fromString(final String jid) throws InvalidJidException { - return Jid.fromString(jid, false); - } - - public static Jid fromString(final String jid, final boolean safe) throws InvalidJidException { - return new Jid(jid, safe); - } - - public static Jid fromParts(final String localpart, - final String domainpart, - final String resourcepart) throws InvalidJidException { - String out; - if (localpart == null || localpart.isEmpty()) { - out = domainpart; - } else { - out = localpart + "@" + domainpart; - } - if (resourcepart != null && !resourcepart.isEmpty()) { - out = out + "/" + resourcepart; - } - return new Jid(out, false); - } - - private Jid(final String jid, final boolean safe) throws InvalidJidException { - if (jid == null) throw new InvalidJidException(InvalidJidException.IS_NULL); - - Jid fromCache = Jid.cache.get(jid); - if (fromCache != null) { + public String getLocalpart() { + return localpart; + } + + public String getDomainpart() { + return IDN.toUnicode(domainpart); + } + + public String getResourcepart() { + return resourcepart; + } + + public static Jid fromSessionID(final SessionID id) throws InvalidJidException { + if (id.getUserID().isEmpty()) { + return Jid.fromString(id.getAccountID()); + } else { + return Jid.fromString(id.getAccountID() + "/" + id.getUserID()); + } + } + + public static Jid fromString(final String jid) throws InvalidJidException { + return Jid.fromString(jid, false); + } + + public static Jid fromString(final String jid, final boolean safe) throws InvalidJidException { + return new Jid(jid, safe); + } + + public static Jid fromParts(final String localpart, + final String domainpart, + final String resourcepart) throws InvalidJidException { + String out; + if (localpart == null || localpart.isEmpty()) { + out = domainpart; + } else { + out = localpart + "@" + domainpart; + } + if (resourcepart != null && !resourcepart.isEmpty()) { + out = out + "/" + resourcepart; + } + return new Jid(out, false); + } + + private Jid(final String jid, final boolean safe) throws InvalidJidException { + if (jid == null) throw new InvalidJidException(InvalidJidException.IS_NULL); + + Jid fromCache = Jid.cache.get(jid); + if (fromCache != null) { displayjid = fromCache.displayjid; - localpart = fromCache.localpart; - domainpart = fromCache.domainpart; - resourcepart = fromCache.resourcepart; - return; - } - - // Hackish Android way to count the number of chars in a string... should work everywhere. - final int atCount = jid.length() - jid.replace("@", "").length(); - final int slashCount = jid.length() - jid.replace("/", "").length(); - - // Throw an error if there's anything obvious wrong with the JID... - if (jid.isEmpty() || jid.length() > 3071) { - throw new InvalidJidException(InvalidJidException.INVALID_LENGTH); - } - - // Go ahead and check if the localpart or resourcepart is empty. - if (jid.startsWith("@") || (jid.endsWith("@") && slashCount == 0) || jid.startsWith("/") || (jid.endsWith("/") && slashCount < 2)) { - throw new InvalidJidException(InvalidJidException.INVALID_CHARACTER); - } + localpart = fromCache.localpart; + domainpart = fromCache.domainpart; + resourcepart = fromCache.resourcepart; + return; + } + + // Hackish Android way to count the number of chars in a string... should work everywhere. + final int atCount = jid.length() - jid.replace("@", "").length(); + final int slashCount = jid.length() - jid.replace("/", "").length(); + + // Throw an error if there's anything obvious wrong with the JID... + if (jid.isEmpty() || jid.length() > 3071) { + throw new InvalidJidException(InvalidJidException.INVALID_LENGTH); + } + + // Go ahead and check if the localpart or resourcepart is empty. + if (jid.startsWith("@") || (jid.endsWith("@") && slashCount == 0) || jid.startsWith("/") || (jid.endsWith("/") && slashCount < 2)) { + throw new InvalidJidException(InvalidJidException.INVALID_CHARACTER); + } String finaljid; - final int domainpartStart; - final int atLoc = jid.indexOf("@"); - final int slashLoc = jid.indexOf("/"); - // If there is no "@" in the JID (eg. "example.net" or "example.net/resource") - // or there are one or more "@" signs but they're all in the resourcepart (eg. "example.net/@/rp@"): - if (atCount == 0 || (atCount > 0 && slashLoc != -1 && atLoc > slashLoc)) { - localpart = ""; + final int domainpartStart; + final int atLoc = jid.indexOf("@"); + final int slashLoc = jid.indexOf("/"); + // If there is no "@" in the JID (eg. "example.net" or "example.net/resource") + // or there are one or more "@" signs but they're all in the resourcepart (eg. "example.net/@/rp@"): + if (atCount == 0 || (atCount > 0 && slashLoc != -1 && atLoc > slashLoc)) { + localpart = ""; finaljid = ""; - domainpartStart = 0; - } else { - final String lp = jid.substring(0, atLoc); - try { - localpart = Config.DISABLE_STRING_PREP || safe ? lp : Stringprep.nodeprep(lp); - } catch (final StringprepException e) { - throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e); - } - if (localpart.isEmpty() || localpart.length() > 1023) { - throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH); - } - domainpartStart = atLoc + 1; + domainpartStart = 0; + } else { + final String lp = jid.substring(0, atLoc); + try { + localpart = Config.DISABLE_STRING_PREP || safe ? lp : Stringprep.nodeprep(lp); + } catch (final StringprepException e) { + throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e); + } + if (localpart.isEmpty() || localpart.length() > 1023) { + throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH); + } + domainpartStart = atLoc + 1; finaljid = lp + "@"; - } - - final String dp; - if (slashCount > 0) { - final String rp = jid.substring(slashLoc + 1, jid.length()); - try { - resourcepart = Config.DISABLE_STRING_PREP || safe ? rp : Stringprep.resourceprep(rp); - } catch (final StringprepException e) { - throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e); - } - if (resourcepart.isEmpty() || resourcepart.length() > 1023) { - throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH); - } - try { - dp = IDN.toUnicode(Stringprep.nameprep(jid.substring(domainpartStart, slashLoc)), IDN.USE_STD3_ASCII_RULES); - } catch (final StringprepException e) { - throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e); - } + } + + final String dp; + if (slashCount > 0) { + final String rp = jid.substring(slashLoc + 1, jid.length()); + try { + resourcepart = Config.DISABLE_STRING_PREP || safe ? rp : Stringprep.resourceprep(rp); + } catch (final StringprepException e) { + throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e); + } + if (resourcepart.isEmpty() || resourcepart.length() > 1023) { + throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH); + } + try { + dp = IDN.toUnicode(Stringprep.nameprep(jid.substring(domainpartStart, slashLoc)), IDN.USE_STD3_ASCII_RULES); + } catch (final StringprepException e) { + throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e); + } finaljid = finaljid + dp + "/" + rp; - } else { - resourcepart = ""; - try{ - dp = IDN.toUnicode(Stringprep.nameprep(jid.substring(domainpartStart, jid.length())), IDN.USE_STD3_ASCII_RULES); - } catch (final StringprepException e) { - throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e); - } + } else { + resourcepart = ""; + try { + dp = IDN.toUnicode(Stringprep.nameprep(jid.substring(domainpartStart, jid.length())), IDN.USE_STD3_ASCII_RULES); + } catch (final StringprepException e) { + throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e); + } finaljid = finaljid + dp; } - // Remove trailing "." before storing the domain part. - if (dp.endsWith(".")) { - try { - domainpart = IDN.toASCII(dp.substring(0, dp.length() - 1), IDN.USE_STD3_ASCII_RULES); - } catch (final IllegalArgumentException e) { - throw new InvalidJidException(e); - } - } else { - try { - domainpart = IDN.toASCII(dp, IDN.USE_STD3_ASCII_RULES); - } catch (final IllegalArgumentException e) { - throw new InvalidJidException(e); - } - } - - // TODO: Find a proper domain validation library; validate individual parts, separators, etc. - if (domainpart.isEmpty() || domainpart.length() > 1023) { - throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH); - } - - Jid.cache.put(jid, this); + // Remove trailing "." before storing the domain part. + if (dp.endsWith(".")) { + try { + domainpart = IDN.toASCII(dp.substring(0, dp.length() - 1), IDN.USE_STD3_ASCII_RULES); + } catch (final IllegalArgumentException e) { + throw new InvalidJidException(e); + } + } else { + try { + domainpart = IDN.toASCII(dp, IDN.USE_STD3_ASCII_RULES); + } catch (final IllegalArgumentException e) { + throw new InvalidJidException(e); + } + } + + // TODO: Find a proper domain validation library; validate individual parts, separators, etc. + if (domainpart.isEmpty() || domainpart.length() > 1023) { + throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH); + } + + Jid.cache.put(jid, this); this.displayjid = finaljid; - } - - public Jid toBareJid() { - try { - return resourcepart.isEmpty() ? this : fromParts(localpart, domainpart, ""); - } catch (final InvalidJidException e) { - // This should never happen. - throw new AssertionError("Jid " + this.toString() + " invalid"); - } - } - - public Jid toDomainJid() { - try { - return resourcepart.isEmpty() && localpart.isEmpty() ? this : fromString(getDomainpart()); - } catch (final InvalidJidException e) { - // This should never happen. - throw new AssertionError("Jid " + this.toString() + " invalid"); - } - } - - @Override - public String toString() { + } + + public Jid toBareJid() { + try { + return resourcepart.isEmpty() ? this : fromParts(localpart, domainpart, ""); + } catch (final InvalidJidException e) { + // This should never happen. + throw new AssertionError("Jid " + this.toString() + " invalid"); + } + } + + public Jid toDomainJid() { + try { + return resourcepart.isEmpty() && localpart.isEmpty() ? this : fromString(getDomainpart()); + } catch (final InvalidJidException e) { + // This should never happen. + throw new AssertionError("Jid " + this.toString() + " invalid"); + } + } + + @Override + public String toString() { return displayjid; } public String toPreppedString() { - String out; - if (hasLocalpart()) { - out = localpart + '@' + domainpart; - } else { - out = domainpart; - } - if (!resourcepart.isEmpty()) { - out += '/'+resourcepart; - } - return out; - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - final Jid jid = (Jid) o; - - return jid.hashCode() == this.hashCode(); - } - - @Override - public int hashCode() { - int result = localpart.hashCode(); - result = 31 * result + domainpart.hashCode(); - result = 31 * result + resourcepart.hashCode(); - return result; - } - - public boolean hasLocalpart() { - return !localpart.isEmpty(); - } - - public boolean isBareJid() { - return this.resourcepart.isEmpty(); - } - - public boolean isDomainJid() { - return !this.hasLocalpart(); - } + String out; + if (hasLocalpart()) { + out = localpart + '@' + domainpart; + } else { + out = domainpart; + } + if (!resourcepart.isEmpty()) { + out += '/' + resourcepart; + } + return out; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final Jid jid = (Jid) o; + + return jid.hashCode() == this.hashCode(); + } + + @Override + public int hashCode() { + int result = localpart.hashCode(); + result = 31 * result + domainpart.hashCode(); + result = 31 * result + resourcepart.hashCode(); + return result; + } + + public boolean hasLocalpart() { + return !localpart.isEmpty(); + } + + public boolean isBareJid() { + return this.resourcepart.isEmpty(); + } + + public boolean isDomainJid() { + return !this.hasLocalpart(); + } } diff --git a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleCandidate.java b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleCandidate.java index 166fa6c69..6f87db473 100644 --- a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleCandidate.java +++ b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleCandidate.java @@ -8,57 +8,57 @@ import de.pixart.messenger.xmpp.jid.Jid; public class JingleCandidate { - public static int TYPE_UNKNOWN; - public static int TYPE_DIRECT = 0; - public static int TYPE_PROXY = 1; - - private boolean ours; - private boolean usedByCounterpart = false; - private String cid; - private String host; - private int port; - private int type; - private Jid jid; - private int priority; - - public JingleCandidate(String cid, boolean ours) { - this.ours = ours; - this.cid = cid; - } - - public String getCid() { - return cid; - } - - public void setHost(String host) { - this.host = host; - } - - public String getHost() { - return this.host; - } - - public void setJid(final Jid jid) { - this.jid = jid; - } - - public Jid getJid() { - return this.jid; - } - - public void setPort(int port) { - this.port = port; - } - - public int getPort() { - return this.port; - } - - public void setType(int type) { - this.type = type; - } - - public void setType(String type) { + public static int TYPE_UNKNOWN; + public static int TYPE_DIRECT = 0; + public static int TYPE_PROXY = 1; + + private boolean ours; + private boolean usedByCounterpart = false; + private String cid; + private String host; + private int port; + private int type; + private Jid jid; + private int priority; + + public JingleCandidate(String cid, boolean ours) { + this.ours = ours; + this.cid = cid; + } + + public String getCid() { + return cid; + } + + public void setHost(String host) { + this.host = host; + } + + public String getHost() { + return this.host; + } + + public void setJid(final Jid jid) { + this.jid = jid; + } + + public Jid getJid() { + return this.jid; + } + + public void setPort(int port) { + this.port = port; + } + + public int getPort() { + return this.port; + } + + public void setType(int type) { + this.type = type; + } + + public void setType(String type) { switch (type) { case "proxy": this.type = TYPE_PROXY; @@ -70,78 +70,78 @@ public class JingleCandidate { this.type = TYPE_UNKNOWN; break; } - } - - public void setPriority(int i) { - this.priority = i; - } - - public int getPriority() { - return this.priority; - } - - public boolean equals(JingleCandidate other) { - return this.getCid().equals(other.getCid()); - } - - public boolean equalValues(JingleCandidate other) { - return other != null && other.getHost().equals(this.getHost()) && (other.getPort() == this.getPort()); - } - - public boolean isOurs() { - return ours; - } - - public int getType() { - return this.type; - } - - public static List parse(List canditates) { - List parsedCandidates = new ArrayList<>(); - for (Element c : canditates) { - parsedCandidates.add(JingleCandidate.parse(c)); - } - return parsedCandidates; - } - - public static JingleCandidate parse(Element candidate) { - JingleCandidate parsedCandidate = new JingleCandidate( - candidate.getAttribute("cid"), false); - parsedCandidate.setHost(candidate.getAttribute("host")); - parsedCandidate.setJid(candidate.getAttributeAsJid("jid")); - parsedCandidate.setType(candidate.getAttribute("type")); - parsedCandidate.setPriority(Integer.parseInt(candidate - .getAttribute("priority"))); - parsedCandidate - .setPort(Integer.parseInt(candidate.getAttribute("port"))); - return parsedCandidate; - } - - public Element toElement() { - Element element = new Element("candidate"); - element.setAttribute("cid", this.getCid()); - element.setAttribute("host", this.getHost()); - element.setAttribute("port", Integer.toString(this.getPort())); - element.setAttribute("jid", this.getJid().toString()); - element.setAttribute("priority", Integer.toString(this.getPriority())); - if (this.getType() == TYPE_DIRECT) { - element.setAttribute("type", "direct"); - } else if (this.getType() == TYPE_PROXY) { - element.setAttribute("type", "proxy"); - } - return element; - } - - public void flagAsUsedByCounterpart() { - this.usedByCounterpart = true; - } - - public boolean isUsedByCounterpart() { - return this.usedByCounterpart; - } - - public String toString() { - return this.getHost() + ":" + this.getPort() + " (prio=" - + this.getPriority() + ")"; - } + } + + public void setPriority(int i) { + this.priority = i; + } + + public int getPriority() { + return this.priority; + } + + public boolean equals(JingleCandidate other) { + return this.getCid().equals(other.getCid()); + } + + public boolean equalValues(JingleCandidate other) { + return other != null && other.getHost().equals(this.getHost()) && (other.getPort() == this.getPort()); + } + + public boolean isOurs() { + return ours; + } + + public int getType() { + return this.type; + } + + public static List parse(List canditates) { + List parsedCandidates = new ArrayList<>(); + for (Element c : canditates) { + parsedCandidates.add(JingleCandidate.parse(c)); + } + return parsedCandidates; + } + + public static JingleCandidate parse(Element candidate) { + JingleCandidate parsedCandidate = new JingleCandidate( + candidate.getAttribute("cid"), false); + parsedCandidate.setHost(candidate.getAttribute("host")); + parsedCandidate.setJid(candidate.getAttributeAsJid("jid")); + parsedCandidate.setType(candidate.getAttribute("type")); + parsedCandidate.setPriority(Integer.parseInt(candidate + .getAttribute("priority"))); + parsedCandidate + .setPort(Integer.parseInt(candidate.getAttribute("port"))); + return parsedCandidate; + } + + public Element toElement() { + Element element = new Element("candidate"); + element.setAttribute("cid", this.getCid()); + element.setAttribute("host", this.getHost()); + element.setAttribute("port", Integer.toString(this.getPort())); + element.setAttribute("jid", this.getJid().toString()); + element.setAttribute("priority", Integer.toString(this.getPriority())); + if (this.getType() == TYPE_DIRECT) { + element.setAttribute("type", "direct"); + } else if (this.getType() == TYPE_PROXY) { + element.setAttribute("type", "proxy"); + } + return element; + } + + public void flagAsUsedByCounterpart() { + this.usedByCounterpart = true; + } + + public boolean isUsedByCounterpart() { + return this.usedByCounterpart; + } + + public String toString() { + return this.getHost() + ":" + this.getPort() + " (prio=" + + this.getPriority() + ")"; + } } diff --git a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnection.java b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnection.java index 5417b8deb..29b9db382 100644 --- a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnection.java +++ b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnection.java @@ -40,1048 +40,1048 @@ import de.pixart.messenger.xmpp.jingle.stanzas.Reason; import de.pixart.messenger.xmpp.stanzas.IqPacket; public class JingleConnection implements Transferable { - private final SimpleDateFormat fileDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US); + private final SimpleDateFormat fileDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US); - private JingleConnectionManager mJingleConnectionManager; - private XmppConnectionService mXmppConnectionService; + private JingleConnectionManager mJingleConnectionManager; + private XmppConnectionService mXmppConnectionService; - protected static final int JINGLE_STATUS_INITIATED = 0; - protected static final int JINGLE_STATUS_ACCEPTED = 1; - protected static final int JINGLE_STATUS_FINISHED = 4; - protected static final int JINGLE_STATUS_TRANSMITTING = 5; - protected static final int JINGLE_STATUS_FAILED = 99; + protected static final int JINGLE_STATUS_INITIATED = 0; + protected static final int JINGLE_STATUS_ACCEPTED = 1; + protected static final int JINGLE_STATUS_FINISHED = 4; + protected static final int JINGLE_STATUS_TRANSMITTING = 5; + protected static final int JINGLE_STATUS_FAILED = 99; - private Content.Version ftVersion = Content.Version.FT_3; + private Content.Version ftVersion = Content.Version.FT_3; - private int ibbBlockSize = 8192; + private int ibbBlockSize = 8192; - private int mJingleStatus = -1; - private int mStatus = Transferable.STATUS_UNKNOWN; - private Message message; - private String sessionId; - private Account account; - private Jid initiator; - private Jid responder; - private List candidates = new ArrayList<>(); - private ConcurrentHashMap connections = new ConcurrentHashMap<>(); + private int mJingleStatus = -1; + private int mStatus = Transferable.STATUS_UNKNOWN; + private Message message; + private String sessionId; + private Account account; + private Jid initiator; + private Jid responder; + private List candidates = new ArrayList<>(); + private ConcurrentHashMap connections = new ConcurrentHashMap<>(); - private String transportId; - private Element fileOffer; - private DownloadableFile file = null; + private String transportId; + private Element fileOffer; + private DownloadableFile file = null; - private String contentName; - private String contentCreator; + private String contentName; + private String contentCreator; - private int mProgress = 0; + private int mProgress = 0; - private boolean receivedCandidate = false; - private boolean sentCandidate = false; + private boolean receivedCandidate = false; + private boolean sentCandidate = false; - private boolean acceptedAutomatically = false; + private boolean acceptedAutomatically = false; - private XmppAxolotlMessage mXmppAxolotlMessage; + private XmppAxolotlMessage mXmppAxolotlMessage; - private JingleTransport transport = null; + private JingleTransport transport = null; - private OutputStream mFileOutputStream; - private InputStream mFileInputStream; + private OutputStream mFileOutputStream; + private InputStream mFileInputStream; - private OnIqPacketReceived responseListener = new OnIqPacketReceived() { + private OnIqPacketReceived responseListener = new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() != IqPacket.TYPE.RESULT) { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() != IqPacket.TYPE.RESULT) { fail(IqParser.extractErrorMessage(packet)); - } - } - }; - - final OnFileTransmissionStatusChanged onFileTransmissionSatusChanged = new OnFileTransmissionStatusChanged() { - - @Override - public void onFileTransmitted(DownloadableFile file) { - if (responder.equals(account.getJid())) { - sendSuccess(); - mXmppConnectionService.getFileBackend().updateFileParams(message); - mXmppConnectionService.databaseBackend.createMessage(message); - mXmppConnectionService.markMessage(message,Message.STATUS_RECEIVED); - if (acceptedAutomatically) { - message.markUnread(); - if (message.getEncryption() == Message.ENCRYPTION_PGP) { - account.getPgpDecryptionService().decrypt(message, true); - } else { - JingleConnection.this.mXmppConnectionService.getNotificationService().push(message); - } - } - } else { - if (message.getEncryption() == Message.ENCRYPTION_PGP) { - account.getPgpDecryptionService().decrypt(message, false); - } - if (message.getEncryption() == Message.ENCRYPTION_PGP || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { - file.delete(); - } - } - Log.d(Config.LOGTAG,"successfully transmitted file:" + file.getAbsolutePath()+" ("+file.getSha1Sum()+")"); - if (message.getEncryption() != Message.ENCRYPTION_PGP) { - mXmppConnectionService.getFileBackend().updateMediaScanner(file); - } - } - - @Override - public void onFileTransferAborted() { - JingleConnection.this.sendCancel(); - JingleConnection.this.fail(); - } - }; - - public InputStream getFileInputStream() { - return this.mFileInputStream; - } - - public OutputStream getFileOutputStream() { - return this.mFileOutputStream; - } - - private OnProxyActivated onProxyActivated = new OnProxyActivated() { - - @Override - public void success() { - if (initiator.equals(account.getJid())) { - Log.d(Config.LOGTAG, "we were initiating. sending file"); - transport.send(file, onFileTransmissionSatusChanged); - } else { - transport.receive(file, onFileTransmissionSatusChanged); - Log.d(Config.LOGTAG, "we were responding. receiving file"); - } - } - - @Override - public void failed() { - Log.d(Config.LOGTAG, "proxy activation failed"); - } - }; - - public JingleConnection(JingleConnectionManager mJingleConnectionManager) { - this.mJingleConnectionManager = mJingleConnectionManager; - this.mXmppConnectionService = mJingleConnectionManager - .getXmppConnectionService(); - } - - public String getSessionId() { - return this.sessionId; - } - - public Account getAccount() { - return this.account; - } - - public Jid getCounterPart() { - return this.message.getCounterpart(); - } - - public void deliverPacket(JinglePacket packet) { - boolean returnResult = true; - if (packet.isAction("session-terminate")) { - Reason reason = packet.getReason(); - if (reason != null) { - if (reason.hasChild("cancel")) { - this.fail(); - } else if (reason.hasChild("success")) { - this.receiveSuccess(); - } else { - this.fail(); - } - } else { - this.fail(); - } - } else if (packet.isAction("session-accept")) { - returnResult = receiveAccept(packet); - } else if (packet.isAction("transport-info")) { - returnResult = receiveTransportInfo(packet); - } else if (packet.isAction("transport-replace")) { - if (packet.getJingleContent().hasIbbTransport()) { - returnResult = this.receiveFallbackToIbb(packet); - } else { - returnResult = false; - Log.d(Config.LOGTAG, "trying to fallback to something unknown" - + packet.toString()); - } - } else if (packet.isAction("transport-accept")) { - returnResult = this.receiveTransportAccept(packet); - } else { - Log.d(Config.LOGTAG, "packet arrived in connection. action was " - + packet.getAction()); - returnResult = false; - } - IqPacket response; - if (returnResult) { - response = packet.generateResponse(IqPacket.TYPE.RESULT); - - } else { - response = packet.generateResponse(IqPacket.TYPE.ERROR); - } - mXmppConnectionService.sendIqPacket(account,response,null); - } - - public void init(final Message message) { - if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { - Conversation conversation = message.getConversation(); - conversation.getAccount().getAxolotlService().prepareKeyTransportMessage(conversation, new OnMessageCreatedCallback() { - @Override - public void run(XmppAxolotlMessage xmppAxolotlMessage) { - if (xmppAxolotlMessage != null) { - init(message, xmppAxolotlMessage); - } else { - fail(); - } - } - }); - } else { - init(message, null); - } - } - - private void init(Message message, XmppAxolotlMessage xmppAxolotlMessage) { - this.mXmppAxolotlMessage = xmppAxolotlMessage; - this.contentCreator = "initiator"; - this.contentName = this.mJingleConnectionManager.nextRandomId(); - this.message = message; - this.account = message.getConversation().getAccount(); - upgradeNamespace(); - this.message.setTransferable(this); - this.mStatus = Transferable.STATUS_UPLOADING; - this.initiator = this.account.getJid(); - this.responder = this.message.getCounterpart(); - this.sessionId = this.mJingleConnectionManager.nextRandomId(); - this.transportId = this.mJingleConnectionManager.nextRandomId(); - if (this.candidates.size() > 0) { - this.sendInitRequest(); - } else { - this.mJingleConnectionManager.getPrimaryCandidate(account, - new OnPrimaryCandidateFound() { - - @Override - public void onPrimaryCandidateFound(boolean success, - final JingleCandidate candidate) { - if (success) { - final JingleSocks5Transport socksConnection = new JingleSocks5Transport( - JingleConnection.this, candidate); - connections.put(candidate.getCid(), - socksConnection); - socksConnection - .connect(new OnTransportConnected() { - - @Override - public void failed() { - Log.d(Config.LOGTAG, - "connection to our own primary candidete failed"); - sendInitRequest(); - } - - @Override - public void established() { - Log.d(Config.LOGTAG, - "successfully connected to our own primary candidate"); - mergeCandidate(candidate); - sendInitRequest(); - } - }); - mergeCandidate(candidate); - } else { - Log.d(Config.LOGTAG, "no primary candidate of our own was found"); - sendInitRequest(); - } - } - }); - } - - } - - private void upgradeNamespace() { - Jid jid = this.message.getCounterpart(); - String resource = jid != null ?jid.getResourcepart() : null; - if (resource != null) { - Presence presence = this.account.getRoster().getContact(jid).getPresences().getPresences().get(resource); - ServiceDiscoveryResult result = presence != null ? presence.getServiceDiscoveryResult() : null; - if (result != null) { - List features = result.getFeatures(); - if (features.contains(Content.Version.FT_4.getNamespace())) { - this.ftVersion = Content.Version.FT_4; - } - } - } - } - - public void init(Account account, JinglePacket packet) { - this.mJingleStatus = JINGLE_STATUS_INITIATED; - Conversation conversation = this.mXmppConnectionService - .findOrCreateConversation(account, - packet.getFrom().toBareJid(), false); - this.message = new Message(conversation, "", Message.ENCRYPTION_NONE); - this.message.setStatus(Message.STATUS_RECEIVED); - this.mStatus = Transferable.STATUS_OFFER; - this.message.setTransferable(this); + } + } + }; + + final OnFileTransmissionStatusChanged onFileTransmissionSatusChanged = new OnFileTransmissionStatusChanged() { + + @Override + public void onFileTransmitted(DownloadableFile file) { + if (responder.equals(account.getJid())) { + sendSuccess(); + mXmppConnectionService.getFileBackend().updateFileParams(message); + mXmppConnectionService.databaseBackend.createMessage(message); + mXmppConnectionService.markMessage(message, Message.STATUS_RECEIVED); + if (acceptedAutomatically) { + message.markUnread(); + if (message.getEncryption() == Message.ENCRYPTION_PGP) { + account.getPgpDecryptionService().decrypt(message, true); + } else { + JingleConnection.this.mXmppConnectionService.getNotificationService().push(message); + } + } + } else { + if (message.getEncryption() == Message.ENCRYPTION_PGP) { + account.getPgpDecryptionService().decrypt(message, false); + } + if (message.getEncryption() == Message.ENCRYPTION_PGP || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { + file.delete(); + } + } + Log.d(Config.LOGTAG, "successfully transmitted file:" + file.getAbsolutePath() + " (" + file.getSha1Sum() + ")"); + if (message.getEncryption() != Message.ENCRYPTION_PGP) { + mXmppConnectionService.getFileBackend().updateMediaScanner(file); + } + } + + @Override + public void onFileTransferAborted() { + JingleConnection.this.sendCancel(); + JingleConnection.this.fail(); + } + }; + + public InputStream getFileInputStream() { + return this.mFileInputStream; + } + + public OutputStream getFileOutputStream() { + return this.mFileOutputStream; + } + + private OnProxyActivated onProxyActivated = new OnProxyActivated() { + + @Override + public void success() { + if (initiator.equals(account.getJid())) { + Log.d(Config.LOGTAG, "we were initiating. sending file"); + transport.send(file, onFileTransmissionSatusChanged); + } else { + transport.receive(file, onFileTransmissionSatusChanged); + Log.d(Config.LOGTAG, "we were responding. receiving file"); + } + } + + @Override + public void failed() { + Log.d(Config.LOGTAG, "proxy activation failed"); + } + }; + + public JingleConnection(JingleConnectionManager mJingleConnectionManager) { + this.mJingleConnectionManager = mJingleConnectionManager; + this.mXmppConnectionService = mJingleConnectionManager + .getXmppConnectionService(); + } + + public String getSessionId() { + return this.sessionId; + } + + public Account getAccount() { + return this.account; + } + + public Jid getCounterPart() { + return this.message.getCounterpart(); + } + + public void deliverPacket(JinglePacket packet) { + boolean returnResult = true; + if (packet.isAction("session-terminate")) { + Reason reason = packet.getReason(); + if (reason != null) { + if (reason.hasChild("cancel")) { + this.fail(); + } else if (reason.hasChild("success")) { + this.receiveSuccess(); + } else { + this.fail(); + } + } else { + this.fail(); + } + } else if (packet.isAction("session-accept")) { + returnResult = receiveAccept(packet); + } else if (packet.isAction("transport-info")) { + returnResult = receiveTransportInfo(packet); + } else if (packet.isAction("transport-replace")) { + if (packet.getJingleContent().hasIbbTransport()) { + returnResult = this.receiveFallbackToIbb(packet); + } else { + returnResult = false; + Log.d(Config.LOGTAG, "trying to fallback to something unknown" + + packet.toString()); + } + } else if (packet.isAction("transport-accept")) { + returnResult = this.receiveTransportAccept(packet); + } else { + Log.d(Config.LOGTAG, "packet arrived in connection. action was " + + packet.getAction()); + returnResult = false; + } + IqPacket response; + if (returnResult) { + response = packet.generateResponse(IqPacket.TYPE.RESULT); + + } else { + response = packet.generateResponse(IqPacket.TYPE.ERROR); + } + mXmppConnectionService.sendIqPacket(account, response, null); + } + + public void init(final Message message) { + if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { + Conversation conversation = message.getConversation(); + conversation.getAccount().getAxolotlService().prepareKeyTransportMessage(conversation, new OnMessageCreatedCallback() { + @Override + public void run(XmppAxolotlMessage xmppAxolotlMessage) { + if (xmppAxolotlMessage != null) { + init(message, xmppAxolotlMessage); + } else { + fail(); + } + } + }); + } else { + init(message, null); + } + } + + private void init(Message message, XmppAxolotlMessage xmppAxolotlMessage) { + this.mXmppAxolotlMessage = xmppAxolotlMessage; + this.contentCreator = "initiator"; + this.contentName = this.mJingleConnectionManager.nextRandomId(); + this.message = message; + this.account = message.getConversation().getAccount(); + upgradeNamespace(); + this.message.setTransferable(this); + this.mStatus = Transferable.STATUS_UPLOADING; + this.initiator = this.account.getJid(); + this.responder = this.message.getCounterpart(); + this.sessionId = this.mJingleConnectionManager.nextRandomId(); + this.transportId = this.mJingleConnectionManager.nextRandomId(); + if (this.candidates.size() > 0) { + this.sendInitRequest(); + } else { + this.mJingleConnectionManager.getPrimaryCandidate(account, + new OnPrimaryCandidateFound() { + + @Override + public void onPrimaryCandidateFound(boolean success, + final JingleCandidate candidate) { + if (success) { + final JingleSocks5Transport socksConnection = new JingleSocks5Transport( + JingleConnection.this, candidate); + connections.put(candidate.getCid(), + socksConnection); + socksConnection + .connect(new OnTransportConnected() { + + @Override + public void failed() { + Log.d(Config.LOGTAG, + "connection to our own primary candidete failed"); + sendInitRequest(); + } + + @Override + public void established() { + Log.d(Config.LOGTAG, + "successfully connected to our own primary candidate"); + mergeCandidate(candidate); + sendInitRequest(); + } + }); + mergeCandidate(candidate); + } else { + Log.d(Config.LOGTAG, "no primary candidate of our own was found"); + sendInitRequest(); + } + } + }); + } + + } + + private void upgradeNamespace() { + Jid jid = this.message.getCounterpart(); + String resource = jid != null ? jid.getResourcepart() : null; + if (resource != null) { + Presence presence = this.account.getRoster().getContact(jid).getPresences().getPresences().get(resource); + ServiceDiscoveryResult result = presence != null ? presence.getServiceDiscoveryResult() : null; + if (result != null) { + List features = result.getFeatures(); + if (features.contains(Content.Version.FT_4.getNamespace())) { + this.ftVersion = Content.Version.FT_4; + } + } + } + } + + public void init(Account account, JinglePacket packet) { + this.mJingleStatus = JINGLE_STATUS_INITIATED; + Conversation conversation = this.mXmppConnectionService + .findOrCreateConversation(account, + packet.getFrom().toBareJid(), false); + this.message = new Message(conversation, "", Message.ENCRYPTION_NONE); + this.message.setStatus(Message.STATUS_RECEIVED); + this.mStatus = Transferable.STATUS_OFFER; + this.message.setTransferable(this); final Jid from = packet.getFrom(); - this.message.setCounterpart(from); - this.account = account; - this.initiator = packet.getFrom(); - this.responder = this.account.getJid(); - this.sessionId = packet.getSessionId(); - Content content = packet.getJingleContent(); - this.contentCreator = content.getAttribute("creator"); - this.contentName = content.getAttribute("name"); - this.transportId = content.getTransportId(); - this.mergeCandidates(JingleCandidate.parse(content.socks5transport().getChildren())); - this.ftVersion = content.getVersion(); - if (ftVersion == null) { - this.sendCancel(); - this.fail(); - return; - } - this.fileOffer = content.getFileOffer(this.ftVersion); - - mXmppConnectionService.sendIqPacket(account,packet.generateResponse(IqPacket.TYPE.RESULT),null); - - if (fileOffer != null) { - Element encrypted = fileOffer.findChild("encrypted", AxolotlService.PEP_PREFIX); - if (encrypted != null) { - this.mXmppAxolotlMessage = XmppAxolotlMessage.fromElement(encrypted, packet.getFrom().toBareJid()); - } - Element fileSize = fileOffer.findChild("size"); - Element fileNameElement = fileOffer.findChild("name"); - if (fileNameElement != null) { - String[] filename = fileNameElement.getContent() - .toLowerCase(Locale.US).toLowerCase().split("\\."); - String filename_new = fileDateFormat.format(new Date(message.getTimeSent()))+"_"+message.getUuid().substring(0,4); - String extension = filename[filename.length - 1]; - if (VALID_IMAGE_EXTENSIONS.contains(extension)) { - message.setType(Message.TYPE_IMAGE); - message.setRelativeFilePath(filename_new+"."+extension); - } else if (VALID_CRYPTO_EXTENSIONS.contains( - filename[filename.length - 1])) { - if (filename.length == 3) { - extension = filename[filename.length - 2]; - if (VALID_IMAGE_EXTENSIONS.contains(extension)) { - message.setType(Message.TYPE_IMAGE); - message.setRelativeFilePath(filename_new+"."+extension); - } else { - message.setType(Message.TYPE_FILE); - } - if (filename[filename.length - 1].equals("otr")) { - message.setEncryption(Message.ENCRYPTION_OTR); - } else { - message.setEncryption(Message.ENCRYPTION_PGP); - } - } - } else { - message.setType(Message.TYPE_FILE); - } - if (message.getType() == Message.TYPE_FILE) { - String suffix = ""; - if (!fileNameElement.getContent().isEmpty()) { - String parts[] = fileNameElement.getContent().split("/"); - suffix = parts[parts.length - 1]; - if (message.getEncryption() == Message.ENCRYPTION_OTR && suffix.endsWith(".otr")) { - suffix = suffix.substring(0,suffix.length() - 4); - } else if (message.getEncryption() == Message.ENCRYPTION_PGP && (suffix.endsWith(".pgp") || suffix.endsWith(".gpg"))) { - suffix = suffix.substring(0,suffix.length() - 4); - } - } - message.setRelativeFilePath(filename_new+"_"+suffix); - } - long size = Long.parseLong(fileSize.getContent()); - message.setBody(Long.toString(size)); - conversation.add(message); - mXmppConnectionService.updateConversationUi(); - if (mJingleConnectionManager.hasStoragePermission() - && size < this.mJingleConnectionManager.getAutoAcceptFileSize() - && mXmppConnectionService.isDataSaverDisabled()) { - Log.d(Config.LOGTAG, "auto accepting file from "+ packet.getFrom()); - this.acceptedAutomatically = true; - this.sendAccept(); - } else { - message.markUnread(); - Log.d(Config.LOGTAG, - "not auto accepting new file offer with size: " - + size - + " allowed size:" - + this.mJingleConnectionManager - .getAutoAcceptFileSize()); - this.mXmppConnectionService.getNotificationService().push(message); - } - this.file = this.mXmppConnectionService.getFileBackend().getFile(message, false); - if (mXmppAxolotlMessage != null) { - XmppAxolotlMessage.XmppAxolotlKeyTransportMessage transportMessage = account.getAxolotlService().processReceivingKeyTransportMessage(mXmppAxolotlMessage); - if (transportMessage != null) { - message.setEncryption(Message.ENCRYPTION_AXOLOTL); - this.file.setKey(transportMessage.getKey()); - this.file.setIv(transportMessage.getIv()); - message.setFingerprint(transportMessage.getFingerprint()); - } else { - Log.d(Config.LOGTAG,"could not process KeyTransportMessage"); - } - } else if (message.getEncryption() == Message.ENCRYPTION_OTR) { - byte[] key = conversation.getSymmetricKey(); - if (key == null) { - this.sendCancel(); - this.fail(); - return; - } else { - this.file.setKeyAndIv(key); - } - } - this.mFileOutputStream = AbstractConnectionManager.createOutputStream(this.file,message.getEncryption() == Message.ENCRYPTION_AXOLOTL); - if (message.getEncryption() == Message.ENCRYPTION_OTR && Config.REPORT_WRONG_FILESIZE_IN_OTR_JINGLE) { - this.file.setExpectedSize((size / 16 + 1) * 16); - } else { - this.file.setExpectedSize(size); - } - Log.d(Config.LOGTAG, "receiving file: expecting size of " + this.file.getExpectedSize()); - } else { - this.sendCancel(); - this.fail(); - } - } else { - this.sendCancel(); - this.fail(); - } - } - - private void sendInitRequest() { - JinglePacket packet = this.bootstrapPacket("session-initiate"); - Content content = new Content(this.contentCreator, this.contentName); - if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) { - content.setTransportId(this.transportId); - this.file = this.mXmppConnectionService.getFileBackend().getFile(message, false); - Pair pair; - try { - if (message.getEncryption() == Message.ENCRYPTION_OTR) { - Conversation conversation = this.message.getConversation(); - if (!this.mXmppConnectionService.renewSymmetricKey(conversation)) { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not set symmetric key"); - cancel(); - } - this.file.setKeyAndIv(conversation.getSymmetricKey()); - pair = AbstractConnectionManager.createInputStream(this.file, false); - this.file.setExpectedSize(pair.second); - content.setFileOffer(this.file, true, this.ftVersion); - } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { - this.file.setKey(mXmppAxolotlMessage.getInnerKey()); - this.file.setIv(mXmppAxolotlMessage.getIV()); - pair = AbstractConnectionManager.createInputStream(this.file, true); - this.file.setExpectedSize(pair.second); - content.setFileOffer(this.file, false, this.ftVersion).addChild(mXmppAxolotlMessage.toElement()); - } else { - pair = AbstractConnectionManager.createInputStream(this.file, false); - this.file.setExpectedSize(pair.second); - content.setFileOffer(this.file, false, this.ftVersion); - } - } catch (FileNotFoundException e) { - cancel(); - return; - } - this.mFileInputStream = pair.first; - content.setTransportId(this.transportId); - content.socks5transport().setChildren(getCandidatesAsElements()); - packet.setContent(content); - this.sendJinglePacket(packet,new OnIqPacketReceived() { - - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": other party received offer"); - mJingleStatus = JINGLE_STATUS_INITIATED; - mXmppConnectionService.markMessage(message, Message.STATUS_OFFERED); - } else { - fail(IqParser.extractErrorMessage(packet)); - } - } - }); - - } - } - - private List getCandidatesAsElements() { - List elements = new ArrayList<>(); - for (JingleCandidate c : this.candidates) { - if (c.isOurs()) { - elements.add(c.toElement()); - } - } - return elements; - } - - private void sendAccept() { - mJingleStatus = JINGLE_STATUS_ACCEPTED; - this.mStatus = Transferable.STATUS_DOWNLOADING; - mXmppConnectionService.updateConversationUi(); - this.mJingleConnectionManager.getPrimaryCandidate(this.account, new OnPrimaryCandidateFound() { - @Override - public void onPrimaryCandidateFound(boolean success, final JingleCandidate candidate) { - final JinglePacket packet = bootstrapPacket("session-accept"); - final Content content = new Content(contentCreator,contentName); - content.setFileOffer(fileOffer, ftVersion); - content.setTransportId(transportId); - if (success && candidate != null && !equalCandidateExists(candidate)) { - final JingleSocks5Transport socksConnection = new JingleSocks5Transport( - JingleConnection.this, - candidate); - connections.put(candidate.getCid(), socksConnection); - socksConnection.connect(new OnTransportConnected() { - - @Override - public void failed() { - Log.d(Config.LOGTAG,"connection to our own primary candidate failed"); - content.socks5transport().setChildren(getCandidatesAsElements()); - packet.setContent(content); - sendJinglePacket(packet); - connectNextCandidate(); - } - - @Override - public void established() { - Log.d(Config.LOGTAG, "connected to primary candidate"); - mergeCandidate(candidate); - content.socks5transport().setChildren(getCandidatesAsElements()); - packet.setContent(content); - sendJinglePacket(packet); - connectNextCandidate(); - } - }); - } else { - Log.d(Config.LOGTAG,"did not find a primary candidate for ourself"); - content.socks5transport().setChildren(getCandidatesAsElements()); - packet.setContent(content); - sendJinglePacket(packet); - connectNextCandidate(); - } - } - }); - } - - private JinglePacket bootstrapPacket(String action) { - JinglePacket packet = new JinglePacket(); - packet.setAction(action); - packet.setFrom(account.getJid()); - packet.setTo(this.message.getCounterpart()); - packet.setSessionId(this.sessionId); - packet.setInitiator(this.initiator); - return packet; - } - - private void sendJinglePacket(JinglePacket packet) { - mXmppConnectionService.sendIqPacket(account,packet,responseListener); - } - - private void sendJinglePacket(JinglePacket packet, OnIqPacketReceived callback) { - mXmppConnectionService.sendIqPacket(account,packet,callback); - } - - private boolean receiveAccept(JinglePacket packet) { - Content content = packet.getJingleContent(); - mergeCandidates(JingleCandidate.parse(content.socks5transport() - .getChildren())); - this.mJingleStatus = JINGLE_STATUS_ACCEPTED; - mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND); - this.connectNextCandidate(); - return true; - } - - private boolean receiveTransportInfo(JinglePacket packet) { - Content content = packet.getJingleContent(); - if (content.hasSocks5Transport()) { - if (content.socks5transport().hasChild("activated")) { - if ((this.transport != null) && (this.transport instanceof JingleSocks5Transport)) { - onProxyActivated.success(); - } else { - String cid = content.socks5transport().findChild("activated").getAttribute("cid"); - Log.d(Config.LOGTAG, "received proxy activated (" + cid - + ")prior to choosing our own transport"); - JingleSocks5Transport connection = this.connections.get(cid); - if (connection != null) { - connection.setActivated(true); - } else { - Log.d(Config.LOGTAG, "activated connection not found"); - this.sendCancel(); - this.fail(); - } - } - return true; - } else if (content.socks5transport().hasChild("proxy-error")) { - onProxyActivated.failed(); - return true; - } else if (content.socks5transport().hasChild("candidate-error")) { - Log.d(Config.LOGTAG, "received candidate error"); - this.receivedCandidate = true; - if ((mJingleStatus == JINGLE_STATUS_ACCEPTED) - && (this.sentCandidate)) { - this.connect(); - } - return true; - } else if (content.socks5transport().hasChild("candidate-used")) { - String cid = content.socks5transport() - .findChild("candidate-used").getAttribute("cid"); - if (cid != null) { - Log.d(Config.LOGTAG, "candidate used by counterpart:" + cid); - JingleCandidate candidate = getCandidate(cid); + this.message.setCounterpart(from); + this.account = account; + this.initiator = packet.getFrom(); + this.responder = this.account.getJid(); + this.sessionId = packet.getSessionId(); + Content content = packet.getJingleContent(); + this.contentCreator = content.getAttribute("creator"); + this.contentName = content.getAttribute("name"); + this.transportId = content.getTransportId(); + this.mergeCandidates(JingleCandidate.parse(content.socks5transport().getChildren())); + this.ftVersion = content.getVersion(); + if (ftVersion == null) { + this.sendCancel(); + this.fail(); + return; + } + this.fileOffer = content.getFileOffer(this.ftVersion); + + mXmppConnectionService.sendIqPacket(account, packet.generateResponse(IqPacket.TYPE.RESULT), null); + + if (fileOffer != null) { + Element encrypted = fileOffer.findChild("encrypted", AxolotlService.PEP_PREFIX); + if (encrypted != null) { + this.mXmppAxolotlMessage = XmppAxolotlMessage.fromElement(encrypted, packet.getFrom().toBareJid()); + } + Element fileSize = fileOffer.findChild("size"); + Element fileNameElement = fileOffer.findChild("name"); + if (fileNameElement != null) { + String[] filename = fileNameElement.getContent() + .toLowerCase(Locale.US).toLowerCase().split("\\."); + String filename_new = fileDateFormat.format(new Date(message.getTimeSent())) + "_" + message.getUuid().substring(0, 4); + String extension = filename[filename.length - 1]; + if (VALID_IMAGE_EXTENSIONS.contains(extension)) { + message.setType(Message.TYPE_IMAGE); + message.setRelativeFilePath(filename_new + "." + extension); + } else if (VALID_CRYPTO_EXTENSIONS.contains( + filename[filename.length - 1])) { + if (filename.length == 3) { + extension = filename[filename.length - 2]; + if (VALID_IMAGE_EXTENSIONS.contains(extension)) { + message.setType(Message.TYPE_IMAGE); + message.setRelativeFilePath(filename_new + "." + extension); + } else { + message.setType(Message.TYPE_FILE); + } + if (filename[filename.length - 1].equals("otr")) { + message.setEncryption(Message.ENCRYPTION_OTR); + } else { + message.setEncryption(Message.ENCRYPTION_PGP); + } + } + } else { + message.setType(Message.TYPE_FILE); + } + if (message.getType() == Message.TYPE_FILE) { + String suffix = ""; + if (!fileNameElement.getContent().isEmpty()) { + String parts[] = fileNameElement.getContent().split("/"); + suffix = parts[parts.length - 1]; + if (message.getEncryption() == Message.ENCRYPTION_OTR && suffix.endsWith(".otr")) { + suffix = suffix.substring(0, suffix.length() - 4); + } else if (message.getEncryption() == Message.ENCRYPTION_PGP && (suffix.endsWith(".pgp") || suffix.endsWith(".gpg"))) { + suffix = suffix.substring(0, suffix.length() - 4); + } + } + message.setRelativeFilePath(filename_new + "_" + suffix); + } + long size = Long.parseLong(fileSize.getContent()); + message.setBody(Long.toString(size)); + conversation.add(message); + mXmppConnectionService.updateConversationUi(); + if (mJingleConnectionManager.hasStoragePermission() + && size < this.mJingleConnectionManager.getAutoAcceptFileSize() + && mXmppConnectionService.isDataSaverDisabled()) { + Log.d(Config.LOGTAG, "auto accepting file from " + packet.getFrom()); + this.acceptedAutomatically = true; + this.sendAccept(); + } else { + message.markUnread(); + Log.d(Config.LOGTAG, + "not auto accepting new file offer with size: " + + size + + " allowed size:" + + this.mJingleConnectionManager + .getAutoAcceptFileSize()); + this.mXmppConnectionService.getNotificationService().push(message); + } + this.file = this.mXmppConnectionService.getFileBackend().getFile(message, false); + if (mXmppAxolotlMessage != null) { + XmppAxolotlMessage.XmppAxolotlKeyTransportMessage transportMessage = account.getAxolotlService().processReceivingKeyTransportMessage(mXmppAxolotlMessage); + if (transportMessage != null) { + message.setEncryption(Message.ENCRYPTION_AXOLOTL); + this.file.setKey(transportMessage.getKey()); + this.file.setIv(transportMessage.getIv()); + message.setFingerprint(transportMessage.getFingerprint()); + } else { + Log.d(Config.LOGTAG, "could not process KeyTransportMessage"); + } + } else if (message.getEncryption() == Message.ENCRYPTION_OTR) { + byte[] key = conversation.getSymmetricKey(); + if (key == null) { + this.sendCancel(); + this.fail(); + return; + } else { + this.file.setKeyAndIv(key); + } + } + this.mFileOutputStream = AbstractConnectionManager.createOutputStream(this.file, message.getEncryption() == Message.ENCRYPTION_AXOLOTL); + if (message.getEncryption() == Message.ENCRYPTION_OTR && Config.REPORT_WRONG_FILESIZE_IN_OTR_JINGLE) { + this.file.setExpectedSize((size / 16 + 1) * 16); + } else { + this.file.setExpectedSize(size); + } + Log.d(Config.LOGTAG, "receiving file: expecting size of " + this.file.getExpectedSize()); + } else { + this.sendCancel(); + this.fail(); + } + } else { + this.sendCancel(); + this.fail(); + } + } + + private void sendInitRequest() { + JinglePacket packet = this.bootstrapPacket("session-initiate"); + Content content = new Content(this.contentCreator, this.contentName); + if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) { + content.setTransportId(this.transportId); + this.file = this.mXmppConnectionService.getFileBackend().getFile(message, false); + Pair pair; + try { + if (message.getEncryption() == Message.ENCRYPTION_OTR) { + Conversation conversation = this.message.getConversation(); + if (!this.mXmppConnectionService.renewSymmetricKey(conversation)) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not set symmetric key"); + cancel(); + } + this.file.setKeyAndIv(conversation.getSymmetricKey()); + pair = AbstractConnectionManager.createInputStream(this.file, false); + this.file.setExpectedSize(pair.second); + content.setFileOffer(this.file, true, this.ftVersion); + } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { + this.file.setKey(mXmppAxolotlMessage.getInnerKey()); + this.file.setIv(mXmppAxolotlMessage.getIV()); + pair = AbstractConnectionManager.createInputStream(this.file, true); + this.file.setExpectedSize(pair.second); + content.setFileOffer(this.file, false, this.ftVersion).addChild(mXmppAxolotlMessage.toElement()); + } else { + pair = AbstractConnectionManager.createInputStream(this.file, false); + this.file.setExpectedSize(pair.second); + content.setFileOffer(this.file, false, this.ftVersion); + } + } catch (FileNotFoundException e) { + cancel(); + return; + } + this.mFileInputStream = pair.first; + content.setTransportId(this.transportId); + content.socks5transport().setChildren(getCandidatesAsElements()); + packet.setContent(content); + this.sendJinglePacket(packet, new OnIqPacketReceived() { + + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.RESULT) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": other party received offer"); + mJingleStatus = JINGLE_STATUS_INITIATED; + mXmppConnectionService.markMessage(message, Message.STATUS_OFFERED); + } else { + fail(IqParser.extractErrorMessage(packet)); + } + } + }); + + } + } + + private List getCandidatesAsElements() { + List elements = new ArrayList<>(); + for (JingleCandidate c : this.candidates) { + if (c.isOurs()) { + elements.add(c.toElement()); + } + } + return elements; + } + + private void sendAccept() { + mJingleStatus = JINGLE_STATUS_ACCEPTED; + this.mStatus = Transferable.STATUS_DOWNLOADING; + mXmppConnectionService.updateConversationUi(); + this.mJingleConnectionManager.getPrimaryCandidate(this.account, new OnPrimaryCandidateFound() { + @Override + public void onPrimaryCandidateFound(boolean success, final JingleCandidate candidate) { + final JinglePacket packet = bootstrapPacket("session-accept"); + final Content content = new Content(contentCreator, contentName); + content.setFileOffer(fileOffer, ftVersion); + content.setTransportId(transportId); + if (success && candidate != null && !equalCandidateExists(candidate)) { + final JingleSocks5Transport socksConnection = new JingleSocks5Transport( + JingleConnection.this, + candidate); + connections.put(candidate.getCid(), socksConnection); + socksConnection.connect(new OnTransportConnected() { + + @Override + public void failed() { + Log.d(Config.LOGTAG, "connection to our own primary candidate failed"); + content.socks5transport().setChildren(getCandidatesAsElements()); + packet.setContent(content); + sendJinglePacket(packet); + connectNextCandidate(); + } + + @Override + public void established() { + Log.d(Config.LOGTAG, "connected to primary candidate"); + mergeCandidate(candidate); + content.socks5transport().setChildren(getCandidatesAsElements()); + packet.setContent(content); + sendJinglePacket(packet); + connectNextCandidate(); + } + }); + } else { + Log.d(Config.LOGTAG, "did not find a primary candidate for ourself"); + content.socks5transport().setChildren(getCandidatesAsElements()); + packet.setContent(content); + sendJinglePacket(packet); + connectNextCandidate(); + } + } + }); + } + + private JinglePacket bootstrapPacket(String action) { + JinglePacket packet = new JinglePacket(); + packet.setAction(action); + packet.setFrom(account.getJid()); + packet.setTo(this.message.getCounterpart()); + packet.setSessionId(this.sessionId); + packet.setInitiator(this.initiator); + return packet; + } + + private void sendJinglePacket(JinglePacket packet) { + mXmppConnectionService.sendIqPacket(account, packet, responseListener); + } + + private void sendJinglePacket(JinglePacket packet, OnIqPacketReceived callback) { + mXmppConnectionService.sendIqPacket(account, packet, callback); + } + + private boolean receiveAccept(JinglePacket packet) { + Content content = packet.getJingleContent(); + mergeCandidates(JingleCandidate.parse(content.socks5transport() + .getChildren())); + this.mJingleStatus = JINGLE_STATUS_ACCEPTED; + mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND); + this.connectNextCandidate(); + return true; + } + + private boolean receiveTransportInfo(JinglePacket packet) { + Content content = packet.getJingleContent(); + if (content.hasSocks5Transport()) { + if (content.socks5transport().hasChild("activated")) { + if ((this.transport != null) && (this.transport instanceof JingleSocks5Transport)) { + onProxyActivated.success(); + } else { + String cid = content.socks5transport().findChild("activated").getAttribute("cid"); + Log.d(Config.LOGTAG, "received proxy activated (" + cid + + ")prior to choosing our own transport"); + JingleSocks5Transport connection = this.connections.get(cid); + if (connection != null) { + connection.setActivated(true); + } else { + Log.d(Config.LOGTAG, "activated connection not found"); + this.sendCancel(); + this.fail(); + } + } + return true; + } else if (content.socks5transport().hasChild("proxy-error")) { + onProxyActivated.failed(); + return true; + } else if (content.socks5transport().hasChild("candidate-error")) { + Log.d(Config.LOGTAG, "received candidate error"); + this.receivedCandidate = true; + if ((mJingleStatus == JINGLE_STATUS_ACCEPTED) + && (this.sentCandidate)) { + this.connect(); + } + return true; + } else if (content.socks5transport().hasChild("candidate-used")) { + String cid = content.socks5transport() + .findChild("candidate-used").getAttribute("cid"); + if (cid != null) { + Log.d(Config.LOGTAG, "candidate used by counterpart:" + cid); + JingleCandidate candidate = getCandidate(cid); if (candidate == null) { - Log.d(Config.LOGTAG,"could not find candidate with cid="+cid); + Log.d(Config.LOGTAG, "could not find candidate with cid=" + cid); return false; } - candidate.flagAsUsedByCounterpart(); - this.receivedCandidate = true; - if ((mJingleStatus == JINGLE_STATUS_ACCEPTED) - && (this.sentCandidate)) { - this.connect(); - } else { - Log.d(Config.LOGTAG, - "ignoring because file is already in transmission or we haven't sent our candidate yet"); - } - return true; - } else { - return false; - } - } else { - return false; - } - } else { - return true; - } - } - - private void connect() { - final JingleSocks5Transport connection = chooseConnection(); - this.transport = connection; - if (connection == null) { - Log.d(Config.LOGTAG, "could not find suitable candidate"); - this.disconnectSocks5Connections(); - if (this.initiator.equals(account.getJid())) { - this.sendFallbackToIbb(); - } - } else { - this.mJingleStatus = JINGLE_STATUS_TRANSMITTING; - if (connection.needsActivation()) { - if (connection.getCandidate().isOurs()) { - final String sid; - if (ftVersion == Content.Version.FT_3) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": use session ID instead of transport ID to activate proxy"); - sid = getSessionId(); - } else { - sid = getTransportId(); - } - Log.d(Config.LOGTAG, "candidate " - + connection.getCandidate().getCid() - + " was our proxy. going to activate"); - IqPacket activation = new IqPacket(IqPacket.TYPE.SET); - activation.setTo(connection.getCandidate().getJid()); - activation.query("http://jabber.org/protocol/bytestreams") - .setAttribute("sid", sid); - activation.query().addChild("activate") - .setContent(this.getCounterPart().toString()); - mXmppConnectionService.sendIqPacket(account,activation, - new OnIqPacketReceived() { - - @Override - public void onIqPacketReceived(Account account, - IqPacket packet) { - if (packet.getType() != IqPacket.TYPE.RESULT) { - onProxyActivated.failed(); - } else { - onProxyActivated.success(); - sendProxyActivated(connection.getCandidate().getCid()); - } - } - }); - } else { - Log.d(Config.LOGTAG, - "candidate " - + connection.getCandidate().getCid() - + " was a proxy. waiting for other party to activate"); - } - } else { - if (initiator.equals(account.getJid())) { - Log.d(Config.LOGTAG, "we were initiating. sending file"); - connection.send(file, onFileTransmissionSatusChanged); - } else { - Log.d(Config.LOGTAG, "we were responding. receiving file"); - connection.receive(file, onFileTransmissionSatusChanged); - } - } - } - } - - private JingleSocks5Transport chooseConnection() { - JingleSocks5Transport connection = null; - for (Entry cursor : connections - .entrySet()) { - JingleSocks5Transport currentConnection = cursor.getValue(); - // Log.d(Config.LOGTAG,"comparing candidate: "+currentConnection.getCandidate().toString()); - if (currentConnection.isEstablished() - && (currentConnection.getCandidate().isUsedByCounterpart() || (!currentConnection - .getCandidate().isOurs()))) { - // Log.d(Config.LOGTAG,"is usable"); - if (connection == null) { - connection = currentConnection; - } else { - if (connection.getCandidate().getPriority() < currentConnection - .getCandidate().getPriority()) { - connection = currentConnection; - } else if (connection.getCandidate().getPriority() == currentConnection - .getCandidate().getPriority()) { - // Log.d(Config.LOGTAG,"found two candidates with same priority"); - if (initiator.equals(account.getJid())) { - if (currentConnection.getCandidate().isOurs()) { - connection = currentConnection; - } - } else { - if (!currentConnection.getCandidate().isOurs()) { - connection = currentConnection; - } - } - } - } - } - } - return connection; - } - - private void sendSuccess() { - JinglePacket packet = bootstrapPacket("session-terminate"); - Reason reason = new Reason(); - reason.addChild("success"); - packet.setReason(reason); - this.sendJinglePacket(packet); - this.disconnectSocks5Connections(); - this.mJingleStatus = JINGLE_STATUS_FINISHED; - this.message.setStatus(Message.STATUS_RECEIVED); - this.message.setTransferable(null); - this.mXmppConnectionService.updateMessage(message); - this.mJingleConnectionManager.finishConnection(this); - } - - private void sendFallbackToIbb() { - Log.d(Config.LOGTAG, "sending fallback to ibb"); - JinglePacket packet = this.bootstrapPacket("transport-replace"); - Content content = new Content(this.contentCreator, this.contentName); - this.transportId = this.mJingleConnectionManager.nextRandomId(); - content.setTransportId(this.transportId); - content.ibbTransport().setAttribute("block-size", - Integer.toString(this.ibbBlockSize)); - packet.setContent(content); - this.sendJinglePacket(packet); - } - - private boolean receiveFallbackToIbb(JinglePacket packet) { - Log.d(Config.LOGTAG, "receiving fallack to ibb"); - String receivedBlockSize = packet.getJingleContent().ibbTransport() - .getAttribute("block-size"); - if (receivedBlockSize != null) { - int bs = Integer.parseInt(receivedBlockSize); - if (bs > this.ibbBlockSize) { - this.ibbBlockSize = bs; - } - } - this.transportId = packet.getJingleContent().getTransportId(); - this.transport = new JingleInbandTransport(this, this.transportId, this.ibbBlockSize); - this.transport.receive(file, onFileTransmissionSatusChanged); - JinglePacket answer = bootstrapPacket("transport-accept"); - Content content = new Content("initiator", "a-file-offer"); - content.setTransportId(this.transportId); - content.ibbTransport().setAttribute("block-size",this.ibbBlockSize); - answer.setContent(content); - this.sendJinglePacket(answer); - return true; - } - - private boolean receiveTransportAccept(JinglePacket packet) { - if (packet.getJingleContent().hasIbbTransport()) { - String receivedBlockSize = packet.getJingleContent().ibbTransport() - .getAttribute("block-size"); - if (receivedBlockSize != null) { - int bs = Integer.parseInt(receivedBlockSize); - if (bs > this.ibbBlockSize) { - this.ibbBlockSize = bs; - } - } - this.transport = new JingleInbandTransport(this, this.transportId, this.ibbBlockSize); - this.transport.connect(new OnTransportConnected() { - - @Override - public void failed() { - Log.d(Config.LOGTAG, "ibb open failed"); - } - - @Override - public void established() { - JingleConnection.this.transport.send(file, - onFileTransmissionSatusChanged); - } - }); - return true; - } else { - return false; - } - } - - private void receiveSuccess() { - this.mJingleStatus = JINGLE_STATUS_FINISHED; - this.mXmppConnectionService.markMessage(this.message,Message.STATUS_SEND_RECEIVED); - this.disconnectSocks5Connections(); - if (this.transport != null && this.transport instanceof JingleInbandTransport) { - this.transport.disconnect(); - } - this.message.setTransferable(null); - this.mJingleConnectionManager.finishConnection(this); - } - - public void cancel() { - this.disconnectSocks5Connections(); - if (this.transport != null && this.transport instanceof JingleInbandTransport) { - this.transport.disconnect(); - } - this.sendCancel(); - this.mJingleConnectionManager.finishConnection(this); - if (this.responder.equals(account.getJid())) { - this.message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_FAILED)); - if (this.file!=null) { - file.delete(); - } - this.mXmppConnectionService.updateConversationUi(); - } else { - this.mXmppConnectionService.markMessage(this.message, - Message.STATUS_SEND_FAILED); - this.message.setTransferable(null); - } - } - - private void fail() { + candidate.flagAsUsedByCounterpart(); + this.receivedCandidate = true; + if ((mJingleStatus == JINGLE_STATUS_ACCEPTED) + && (this.sentCandidate)) { + this.connect(); + } else { + Log.d(Config.LOGTAG, + "ignoring because file is already in transmission or we haven't sent our candidate yet"); + } + return true; + } else { + return false; + } + } else { + return false; + } + } else { + return true; + } + } + + private void connect() { + final JingleSocks5Transport connection = chooseConnection(); + this.transport = connection; + if (connection == null) { + Log.d(Config.LOGTAG, "could not find suitable candidate"); + this.disconnectSocks5Connections(); + if (this.initiator.equals(account.getJid())) { + this.sendFallbackToIbb(); + } + } else { + this.mJingleStatus = JINGLE_STATUS_TRANSMITTING; + if (connection.needsActivation()) { + if (connection.getCandidate().isOurs()) { + final String sid; + if (ftVersion == Content.Version.FT_3) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": use session ID instead of transport ID to activate proxy"); + sid = getSessionId(); + } else { + sid = getTransportId(); + } + Log.d(Config.LOGTAG, "candidate " + + connection.getCandidate().getCid() + + " was our proxy. going to activate"); + IqPacket activation = new IqPacket(IqPacket.TYPE.SET); + activation.setTo(connection.getCandidate().getJid()); + activation.query("http://jabber.org/protocol/bytestreams") + .setAttribute("sid", sid); + activation.query().addChild("activate") + .setContent(this.getCounterPart().toString()); + mXmppConnectionService.sendIqPacket(account, activation, + new OnIqPacketReceived() { + + @Override + public void onIqPacketReceived(Account account, + IqPacket packet) { + if (packet.getType() != IqPacket.TYPE.RESULT) { + onProxyActivated.failed(); + } else { + onProxyActivated.success(); + sendProxyActivated(connection.getCandidate().getCid()); + } + } + }); + } else { + Log.d(Config.LOGTAG, + "candidate " + + connection.getCandidate().getCid() + + " was a proxy. waiting for other party to activate"); + } + } else { + if (initiator.equals(account.getJid())) { + Log.d(Config.LOGTAG, "we were initiating. sending file"); + connection.send(file, onFileTransmissionSatusChanged); + } else { + Log.d(Config.LOGTAG, "we were responding. receiving file"); + connection.receive(file, onFileTransmissionSatusChanged); + } + } + } + } + + private JingleSocks5Transport chooseConnection() { + JingleSocks5Transport connection = null; + for (Entry cursor : connections + .entrySet()) { + JingleSocks5Transport currentConnection = cursor.getValue(); + // Log.d(Config.LOGTAG,"comparing candidate: "+currentConnection.getCandidate().toString()); + if (currentConnection.isEstablished() + && (currentConnection.getCandidate().isUsedByCounterpart() || (!currentConnection + .getCandidate().isOurs()))) { + // Log.d(Config.LOGTAG,"is usable"); + if (connection == null) { + connection = currentConnection; + } else { + if (connection.getCandidate().getPriority() < currentConnection + .getCandidate().getPriority()) { + connection = currentConnection; + } else if (connection.getCandidate().getPriority() == currentConnection + .getCandidate().getPriority()) { + // Log.d(Config.LOGTAG,"found two candidates with same priority"); + if (initiator.equals(account.getJid())) { + if (currentConnection.getCandidate().isOurs()) { + connection = currentConnection; + } + } else { + if (!currentConnection.getCandidate().isOurs()) { + connection = currentConnection; + } + } + } + } + } + } + return connection; + } + + private void sendSuccess() { + JinglePacket packet = bootstrapPacket("session-terminate"); + Reason reason = new Reason(); + reason.addChild("success"); + packet.setReason(reason); + this.sendJinglePacket(packet); + this.disconnectSocks5Connections(); + this.mJingleStatus = JINGLE_STATUS_FINISHED; + this.message.setStatus(Message.STATUS_RECEIVED); + this.message.setTransferable(null); + this.mXmppConnectionService.updateMessage(message); + this.mJingleConnectionManager.finishConnection(this); + } + + private void sendFallbackToIbb() { + Log.d(Config.LOGTAG, "sending fallback to ibb"); + JinglePacket packet = this.bootstrapPacket("transport-replace"); + Content content = new Content(this.contentCreator, this.contentName); + this.transportId = this.mJingleConnectionManager.nextRandomId(); + content.setTransportId(this.transportId); + content.ibbTransport().setAttribute("block-size", + Integer.toString(this.ibbBlockSize)); + packet.setContent(content); + this.sendJinglePacket(packet); + } + + private boolean receiveFallbackToIbb(JinglePacket packet) { + Log.d(Config.LOGTAG, "receiving fallack to ibb"); + String receivedBlockSize = packet.getJingleContent().ibbTransport() + .getAttribute("block-size"); + if (receivedBlockSize != null) { + int bs = Integer.parseInt(receivedBlockSize); + if (bs > this.ibbBlockSize) { + this.ibbBlockSize = bs; + } + } + this.transportId = packet.getJingleContent().getTransportId(); + this.transport = new JingleInbandTransport(this, this.transportId, this.ibbBlockSize); + this.transport.receive(file, onFileTransmissionSatusChanged); + JinglePacket answer = bootstrapPacket("transport-accept"); + Content content = new Content("initiator", "a-file-offer"); + content.setTransportId(this.transportId); + content.ibbTransport().setAttribute("block-size", this.ibbBlockSize); + answer.setContent(content); + this.sendJinglePacket(answer); + return true; + } + + private boolean receiveTransportAccept(JinglePacket packet) { + if (packet.getJingleContent().hasIbbTransport()) { + String receivedBlockSize = packet.getJingleContent().ibbTransport() + .getAttribute("block-size"); + if (receivedBlockSize != null) { + int bs = Integer.parseInt(receivedBlockSize); + if (bs > this.ibbBlockSize) { + this.ibbBlockSize = bs; + } + } + this.transport = new JingleInbandTransport(this, this.transportId, this.ibbBlockSize); + this.transport.connect(new OnTransportConnected() { + + @Override + public void failed() { + Log.d(Config.LOGTAG, "ibb open failed"); + } + + @Override + public void established() { + JingleConnection.this.transport.send(file, + onFileTransmissionSatusChanged); + } + }); + return true; + } else { + return false; + } + } + + private void receiveSuccess() { + this.mJingleStatus = JINGLE_STATUS_FINISHED; + this.mXmppConnectionService.markMessage(this.message, Message.STATUS_SEND_RECEIVED); + this.disconnectSocks5Connections(); + if (this.transport != null && this.transport instanceof JingleInbandTransport) { + this.transport.disconnect(); + } + this.message.setTransferable(null); + this.mJingleConnectionManager.finishConnection(this); + } + + public void cancel() { + this.disconnectSocks5Connections(); + if (this.transport != null && this.transport instanceof JingleInbandTransport) { + this.transport.disconnect(); + } + this.sendCancel(); + this.mJingleConnectionManager.finishConnection(this); + if (this.responder.equals(account.getJid())) { + this.message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_FAILED)); + if (this.file != null) { + file.delete(); + } + this.mXmppConnectionService.updateConversationUi(); + } else { + this.mXmppConnectionService.markMessage(this.message, + Message.STATUS_SEND_FAILED); + this.message.setTransferable(null); + } + } + + private void fail() { fail(null); } private void fail(String errorMessage) { - this.mJingleStatus = JINGLE_STATUS_FAILED; - this.disconnectSocks5Connections(); - if (this.transport != null && this.transport instanceof JingleInbandTransport) { - this.transport.disconnect(); - } - FileBackend.close(mFileInputStream); - FileBackend.close(mFileOutputStream); - if (this.message != null) { - if (this.responder.equals(account.getJid())) { - this.message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_FAILED)); - if (this.file!=null) { - file.delete(); - } - this.mXmppConnectionService.updateConversationUi(); - } else { - this.mXmppConnectionService.markMessage(this.message, - Message.STATUS_SEND_FAILED, + this.mJingleStatus = JINGLE_STATUS_FAILED; + this.disconnectSocks5Connections(); + if (this.transport != null && this.transport instanceof JingleInbandTransport) { + this.transport.disconnect(); + } + FileBackend.close(mFileInputStream); + FileBackend.close(mFileOutputStream); + if (this.message != null) { + if (this.responder.equals(account.getJid())) { + this.message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_FAILED)); + if (this.file != null) { + file.delete(); + } + this.mXmppConnectionService.updateConversationUi(); + } else { + this.mXmppConnectionService.markMessage(this.message, + Message.STATUS_SEND_FAILED, errorMessage); - this.message.setTransferable(null); - } - } - this.mJingleConnectionManager.finishConnection(this); - } - - private void sendCancel() { - JinglePacket packet = bootstrapPacket("session-terminate"); - Reason reason = new Reason(); - reason.addChild("cancel"); - packet.setReason(reason); - this.sendJinglePacket(packet); - } - - private void connectNextCandidate() { - for (JingleCandidate candidate : this.candidates) { - if ((!connections.containsKey(candidate.getCid()) && (!candidate - .isOurs()))) { - this.connectWithCandidate(candidate); - return; - } - } - this.sendCandidateError(); - } - - private void connectWithCandidate(final JingleCandidate candidate) { - final JingleSocks5Transport socksConnection = new JingleSocks5Transport( - this, candidate); - connections.put(candidate.getCid(), socksConnection); - socksConnection.connect(new OnTransportConnected() { - - @Override - public void failed() { - Log.d(Config.LOGTAG, - "connection failed with " + candidate.getHost() + ":" - + candidate.getPort()); - connectNextCandidate(); - } - - @Override - public void established() { - Log.d(Config.LOGTAG, - "established connection with " + candidate.getHost() - + ":" + candidate.getPort()); - sendCandidateUsed(candidate.getCid()); - } - }); - } - - private void disconnectSocks5Connections() { - Iterator> it = this.connections - .entrySet().iterator(); - while (it.hasNext()) { - Entry pairs = it.next(); - pairs.getValue().disconnect(); - it.remove(); - } - } - - private void sendProxyActivated(String cid) { - JinglePacket packet = bootstrapPacket("transport-info"); - Content content = new Content(this.contentCreator, this.contentName); - content.setTransportId(this.transportId); - content.socks5transport().addChild("activated") - .setAttribute("cid", cid); - packet.setContent(content); - this.sendJinglePacket(packet); - } - - private void sendCandidateUsed(final String cid) { - JinglePacket packet = bootstrapPacket("transport-info"); - Content content = new Content(this.contentCreator, this.contentName); - content.setTransportId(this.transportId); - content.socks5transport().addChild("candidate-used") - .setAttribute("cid", cid); - packet.setContent(content); - this.sentCandidate = true; - if ((receivedCandidate) && (mJingleStatus == JINGLE_STATUS_ACCEPTED)) { - connect(); - } - this.sendJinglePacket(packet); - } - - private void sendCandidateError() { - JinglePacket packet = bootstrapPacket("transport-info"); - Content content = new Content(this.contentCreator, this.contentName); - content.setTransportId(this.transportId); - content.socks5transport().addChild("candidate-error"); - packet.setContent(content); - this.sentCandidate = true; - if ((receivedCandidate) && (mJingleStatus == JINGLE_STATUS_ACCEPTED)) { - connect(); - } - this.sendJinglePacket(packet); - } - - public Jid getInitiator() { - return this.initiator; - } - - public Jid getResponder() { - return this.responder; - } - - public int getJingleStatus() { - return this.mJingleStatus; - } - - private boolean equalCandidateExists(JingleCandidate candidate) { - for (JingleCandidate c : this.candidates) { - if (c.equalValues(candidate)) { - return true; - } - } - return false; - } - - private void mergeCandidate(JingleCandidate candidate) { - for (JingleCandidate c : this.candidates) { - if (c.equals(candidate)) { - return; - } - } - this.candidates.add(candidate); - } - - private void mergeCandidates(List candidates) { - for (JingleCandidate c : candidates) { - mergeCandidate(c); - } - } - - private JingleCandidate getCandidate(String cid) { - for (JingleCandidate c : this.candidates) { - if (c.getCid().equals(cid)) { - return c; - } - } - return null; - } - - public void updateProgress(int i) { - this.mProgress = i; - mXmppConnectionService.updateConversationUi(); - } - - public String getTransportId() { - return this.transportId; - } - - public Content.Version getFtVersion() { - return this.ftVersion; - } - - interface OnProxyActivated { - public void success(); - - public void failed(); - } - - public boolean hasTransportId(String sid) { - return sid.equals(this.transportId); - } - - public JingleTransport getTransport() { - return this.transport; - } - - public boolean start() { - if (account.getStatus() == Account.State.ONLINE) { - if (mJingleStatus == JINGLE_STATUS_INITIATED) { - new Thread(new Runnable() { - - @Override - public void run() { - sendAccept(); - } - }).start(); - } - return true; - } else { - return false; - } - } - - @Override - public int getStatus() { - return this.mStatus; - } - - @Override - public long getFileSize() { - if (this.file != null) { - return this.file.getExpectedSize(); - } else { - return 0; - } - } - - @Override - public int getProgress() { - return this.mProgress; - } - - public AbstractConnectionManager getConnectionManager() { - return this.mJingleConnectionManager; - } + this.message.setTransferable(null); + } + } + this.mJingleConnectionManager.finishConnection(this); + } + + private void sendCancel() { + JinglePacket packet = bootstrapPacket("session-terminate"); + Reason reason = new Reason(); + reason.addChild("cancel"); + packet.setReason(reason); + this.sendJinglePacket(packet); + } + + private void connectNextCandidate() { + for (JingleCandidate candidate : this.candidates) { + if ((!connections.containsKey(candidate.getCid()) && (!candidate + .isOurs()))) { + this.connectWithCandidate(candidate); + return; + } + } + this.sendCandidateError(); + } + + private void connectWithCandidate(final JingleCandidate candidate) { + final JingleSocks5Transport socksConnection = new JingleSocks5Transport( + this, candidate); + connections.put(candidate.getCid(), socksConnection); + socksConnection.connect(new OnTransportConnected() { + + @Override + public void failed() { + Log.d(Config.LOGTAG, + "connection failed with " + candidate.getHost() + ":" + + candidate.getPort()); + connectNextCandidate(); + } + + @Override + public void established() { + Log.d(Config.LOGTAG, + "established connection with " + candidate.getHost() + + ":" + candidate.getPort()); + sendCandidateUsed(candidate.getCid()); + } + }); + } + + private void disconnectSocks5Connections() { + Iterator> it = this.connections + .entrySet().iterator(); + while (it.hasNext()) { + Entry pairs = it.next(); + pairs.getValue().disconnect(); + it.remove(); + } + } + + private void sendProxyActivated(String cid) { + JinglePacket packet = bootstrapPacket("transport-info"); + Content content = new Content(this.contentCreator, this.contentName); + content.setTransportId(this.transportId); + content.socks5transport().addChild("activated") + .setAttribute("cid", cid); + packet.setContent(content); + this.sendJinglePacket(packet); + } + + private void sendCandidateUsed(final String cid) { + JinglePacket packet = bootstrapPacket("transport-info"); + Content content = new Content(this.contentCreator, this.contentName); + content.setTransportId(this.transportId); + content.socks5transport().addChild("candidate-used") + .setAttribute("cid", cid); + packet.setContent(content); + this.sentCandidate = true; + if ((receivedCandidate) && (mJingleStatus == JINGLE_STATUS_ACCEPTED)) { + connect(); + } + this.sendJinglePacket(packet); + } + + private void sendCandidateError() { + JinglePacket packet = bootstrapPacket("transport-info"); + Content content = new Content(this.contentCreator, this.contentName); + content.setTransportId(this.transportId); + content.socks5transport().addChild("candidate-error"); + packet.setContent(content); + this.sentCandidate = true; + if ((receivedCandidate) && (mJingleStatus == JINGLE_STATUS_ACCEPTED)) { + connect(); + } + this.sendJinglePacket(packet); + } + + public Jid getInitiator() { + return this.initiator; + } + + public Jid getResponder() { + return this.responder; + } + + public int getJingleStatus() { + return this.mJingleStatus; + } + + private boolean equalCandidateExists(JingleCandidate candidate) { + for (JingleCandidate c : this.candidates) { + if (c.equalValues(candidate)) { + return true; + } + } + return false; + } + + private void mergeCandidate(JingleCandidate candidate) { + for (JingleCandidate c : this.candidates) { + if (c.equals(candidate)) { + return; + } + } + this.candidates.add(candidate); + } + + private void mergeCandidates(List candidates) { + for (JingleCandidate c : candidates) { + mergeCandidate(c); + } + } + + private JingleCandidate getCandidate(String cid) { + for (JingleCandidate c : this.candidates) { + if (c.getCid().equals(cid)) { + return c; + } + } + return null; + } + + public void updateProgress(int i) { + this.mProgress = i; + mXmppConnectionService.updateConversationUi(); + } + + public String getTransportId() { + return this.transportId; + } + + public Content.Version getFtVersion() { + return this.ftVersion; + } + + interface OnProxyActivated { + public void success(); + + public void failed(); + } + + public boolean hasTransportId(String sid) { + return sid.equals(this.transportId); + } + + public JingleTransport getTransport() { + return this.transport; + } + + public boolean start() { + if (account.getStatus() == Account.State.ONLINE) { + if (mJingleStatus == JINGLE_STATUS_INITIATED) { + new Thread(new Runnable() { + + @Override + public void run() { + sendAccept(); + } + }).start(); + } + return true; + } else { + return false; + } + } + + @Override + public int getStatus() { + return this.mStatus; + } + + @Override + public long getFileSize() { + if (this.file != null) { + return this.file.getExpectedSize(); + } else { + return 0; + } + } + + @Override + public int getProgress() { + return this.mProgress; + } + + public AbstractConnectionManager getConnectionManager() { + return this.mJingleConnectionManager; + } } diff --git a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnectionManager.java b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnectionManager.java index b8f96bded..d36bc52b0 100644 --- a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnectionManager.java @@ -23,149 +23,149 @@ import de.pixart.messenger.xmpp.jingle.stanzas.JinglePacket; import de.pixart.messenger.xmpp.stanzas.IqPacket; public class JingleConnectionManager extends AbstractConnectionManager { - private List connections = new CopyOnWriteArrayList<>(); - - private HashMap primaryCandidates = new HashMap<>(); - - @SuppressLint("TrulyRandom") - private SecureRandom random = new SecureRandom(); - - public JingleConnectionManager(XmppConnectionService service) { - super(service); - } - - public void deliverPacket(Account account, JinglePacket packet) { - if (packet.isAction("session-initiate")) { - JingleConnection connection = new JingleConnection(this); - connection.init(account, packet); - connections.add(connection); - } else { - for (JingleConnection connection : connections) { - if (connection.getAccount() == account - && connection.getSessionId().equals( - packet.getSessionId()) - && connection.getCounterPart().equals(packet.getFrom())) { - connection.deliverPacket(packet); - return; - } - } - IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR); - Element error = response.addChild("error"); - error.setAttribute("type", "cancel"); - error.addChild("item-not-found", - "urn:ietf:params:xml:ns:xmpp-stanzas"); - error.addChild("unknown-session", "urn:xmpp:jingle:errors:1"); - account.getXmppConnection().sendIqPacket(response, null); - } - } - - public JingleConnection createNewConnection(Message message) { - Transferable old = message.getTransferable(); - if (old != null) { - old.cancel(); - } - JingleConnection connection = new JingleConnection(this); - mXmppConnectionService.markMessage(message,Message.STATUS_WAITING); - connection.init(message); - this.connections.add(connection); - return connection; - } - - public JingleConnection createNewConnection(final JinglePacket packet) { - JingleConnection connection = new JingleConnection(this); - this.connections.add(connection); - return connection; - } - - public void finishConnection(JingleConnection connection) { - this.connections.remove(connection); - } - - public void getPrimaryCandidate(Account account, - final OnPrimaryCandidateFound listener) { - if (Config.DISABLE_PROXY_LOOKUP) { - listener.onPrimaryCandidateFound(false, null); - return; - } - if (!this.primaryCandidates.containsKey(account.getJid().toBareJid())) { - final Jid proxy = account.getXmppConnection().findDiscoItemByFeature(Xmlns.BYTE_STREAMS); - if (proxy != null) { - IqPacket iq = new IqPacket(IqPacket.TYPE.GET); - iq.setTo(proxy); - iq.query(Xmlns.BYTE_STREAMS); - account.getXmppConnection().sendIqPacket(iq,new OnIqPacketReceived() { - - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - Element streamhost = packet.query().findChild("streamhost",Xmlns.BYTE_STREAMS); - final String host = streamhost == null ? null : streamhost.getAttribute("host"); - final String port = streamhost == null ? null : streamhost.getAttribute("port"); - if (host != null && port != null) { - try { - JingleCandidate candidate = new JingleCandidate(nextRandomId(), true); - candidate.setHost(host); - candidate.setPort(Integer.parseInt(port)); - candidate.setType(JingleCandidate.TYPE_PROXY); - candidate.setJid(proxy); - candidate.setPriority(655360 + 65535); - primaryCandidates.put(account.getJid().toBareJid(),candidate); - listener.onPrimaryCandidateFound(true,candidate); - } catch (final NumberFormatException e) { - listener.onPrimaryCandidateFound(false,null); - return; - } - } else { - listener.onPrimaryCandidateFound(false,null); - } - } - }); - } else { - listener.onPrimaryCandidateFound(false, null); - } - - } else { - listener.onPrimaryCandidateFound(true, - this.primaryCandidates.get(account.getJid().toBareJid())); - } - } - - public String nextRandomId() { - return new BigInteger(50, random).toString(32); - } - - public void deliverIbbPacket(Account account, IqPacket packet) { - String sid = null; - Element payload = null; - if (packet.hasChild("open", "http://jabber.org/protocol/ibb")) { - payload = packet.findChild("open", "http://jabber.org/protocol/ibb"); - sid = payload.getAttribute("sid"); - } else if (packet.hasChild("data", "http://jabber.org/protocol/ibb")) { - payload = packet.findChild("data", "http://jabber.org/protocol/ibb"); - sid = payload.getAttribute("sid"); - } - if (sid != null) { - for (JingleConnection connection : connections) { - if (connection.getAccount() == account - && connection.hasTransportId(sid)) { - JingleTransport transport = connection.getTransport(); - if (transport instanceof JingleInbandTransport) { - JingleInbandTransport inbandTransport = (JingleInbandTransport) transport; - inbandTransport.deliverPayload(packet, payload); - return; - } - } - } - Log.d(Config.LOGTAG,"couldn't deliver payload: " + payload.toString()); - } else { - Log.d(Config.LOGTAG, "no sid found in incoming ibb packet"); - } - } - - public void cancelInTransmission() { - for (JingleConnection connection : this.connections) { - if (connection.getJingleStatus() == JingleConnection.JINGLE_STATUS_TRANSMITTING) { - connection.cancel(); - } - } - } + private List connections = new CopyOnWriteArrayList<>(); + + private HashMap primaryCandidates = new HashMap<>(); + + @SuppressLint("TrulyRandom") + private SecureRandom random = new SecureRandom(); + + public JingleConnectionManager(XmppConnectionService service) { + super(service); + } + + public void deliverPacket(Account account, JinglePacket packet) { + if (packet.isAction("session-initiate")) { + JingleConnection connection = new JingleConnection(this); + connection.init(account, packet); + connections.add(connection); + } else { + for (JingleConnection connection : connections) { + if (connection.getAccount() == account + && connection.getSessionId().equals( + packet.getSessionId()) + && connection.getCounterPart().equals(packet.getFrom())) { + connection.deliverPacket(packet); + return; + } + } + IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR); + Element error = response.addChild("error"); + error.setAttribute("type", "cancel"); + error.addChild("item-not-found", + "urn:ietf:params:xml:ns:xmpp-stanzas"); + error.addChild("unknown-session", "urn:xmpp:jingle:errors:1"); + account.getXmppConnection().sendIqPacket(response, null); + } + } + + public JingleConnection createNewConnection(Message message) { + Transferable old = message.getTransferable(); + if (old != null) { + old.cancel(); + } + JingleConnection connection = new JingleConnection(this); + mXmppConnectionService.markMessage(message, Message.STATUS_WAITING); + connection.init(message); + this.connections.add(connection); + return connection; + } + + public JingleConnection createNewConnection(final JinglePacket packet) { + JingleConnection connection = new JingleConnection(this); + this.connections.add(connection); + return connection; + } + + public void finishConnection(JingleConnection connection) { + this.connections.remove(connection); + } + + public void getPrimaryCandidate(Account account, + final OnPrimaryCandidateFound listener) { + if (Config.DISABLE_PROXY_LOOKUP) { + listener.onPrimaryCandidateFound(false, null); + return; + } + if (!this.primaryCandidates.containsKey(account.getJid().toBareJid())) { + final Jid proxy = account.getXmppConnection().findDiscoItemByFeature(Xmlns.BYTE_STREAMS); + if (proxy != null) { + IqPacket iq = new IqPacket(IqPacket.TYPE.GET); + iq.setTo(proxy); + iq.query(Xmlns.BYTE_STREAMS); + account.getXmppConnection().sendIqPacket(iq, new OnIqPacketReceived() { + + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + Element streamhost = packet.query().findChild("streamhost", Xmlns.BYTE_STREAMS); + final String host = streamhost == null ? null : streamhost.getAttribute("host"); + final String port = streamhost == null ? null : streamhost.getAttribute("port"); + if (host != null && port != null) { + try { + JingleCandidate candidate = new JingleCandidate(nextRandomId(), true); + candidate.setHost(host); + candidate.setPort(Integer.parseInt(port)); + candidate.setType(JingleCandidate.TYPE_PROXY); + candidate.setJid(proxy); + candidate.setPriority(655360 + 65535); + primaryCandidates.put(account.getJid().toBareJid(), candidate); + listener.onPrimaryCandidateFound(true, candidate); + } catch (final NumberFormatException e) { + listener.onPrimaryCandidateFound(false, null); + return; + } + } else { + listener.onPrimaryCandidateFound(false, null); + } + } + }); + } else { + listener.onPrimaryCandidateFound(false, null); + } + + } else { + listener.onPrimaryCandidateFound(true, + this.primaryCandidates.get(account.getJid().toBareJid())); + } + } + + public String nextRandomId() { + return new BigInteger(50, random).toString(32); + } + + public void deliverIbbPacket(Account account, IqPacket packet) { + String sid = null; + Element payload = null; + if (packet.hasChild("open", "http://jabber.org/protocol/ibb")) { + payload = packet.findChild("open", "http://jabber.org/protocol/ibb"); + sid = payload.getAttribute("sid"); + } else if (packet.hasChild("data", "http://jabber.org/protocol/ibb")) { + payload = packet.findChild("data", "http://jabber.org/protocol/ibb"); + sid = payload.getAttribute("sid"); + } + if (sid != null) { + for (JingleConnection connection : connections) { + if (connection.getAccount() == account + && connection.hasTransportId(sid)) { + JingleTransport transport = connection.getTransport(); + if (transport instanceof JingleInbandTransport) { + JingleInbandTransport inbandTransport = (JingleInbandTransport) transport; + inbandTransport.deliverPayload(packet, payload); + return; + } + } + } + Log.d(Config.LOGTAG, "couldn't deliver payload: " + payload.toString()); + } else { + Log.d(Config.LOGTAG, "no sid found in incoming ibb packet"); + } + } + + public void cancelInTransmission() { + for (JingleConnection connection : this.connections) { + if (connection.getJingleStatus() == JingleConnection.JINGLE_STATUS_TRANSMITTING) { + connection.cancel(); + } + } + } } diff --git a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleInbandTransport.java b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleInbandTransport.java index 4e45c2a03..b19910c9f 100644 --- a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleInbandTransport.java +++ b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleInbandTransport.java @@ -22,218 +22,218 @@ import de.pixart.messenger.xmpp.stanzas.IqPacket; public class JingleInbandTransport extends JingleTransport { - private Account account; - private Jid counterpart; - private int blockSize; - private int seq = 0; - private String sessionId; - - private boolean established = false; - - private boolean connected = true; - - private DownloadableFile file; - private JingleConnection connection; - - private InputStream fileInputStream = null; - private OutputStream fileOutputStream = null; - private long remainingSize = 0; - private long fileSize = 0; - private MessageDigest digest; - - private OnFileTransmissionStatusChanged onFileTransmissionStatusChanged; - - private OnIqPacketReceived onAckReceived = new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (connected && packet.getType() == IqPacket.TYPE.RESULT) { - sendNextBlock(); - } - } - }; - - public JingleInbandTransport(final JingleConnection connection, final String sid, final int blocksize) { - this.connection = connection; - this.account = connection.getAccount(); - this.counterpart = connection.getCounterPart(); - this.blockSize = blocksize; - this.sessionId = sid; - } - - public void connect(final OnTransportConnected callback) { - IqPacket iq = new IqPacket(IqPacket.TYPE.SET); - iq.setTo(this.counterpart); - Element open = iq.addChild("open", "http://jabber.org/protocol/ibb"); - open.setAttribute("sid", this.sessionId); - open.setAttribute("stanza", "iq"); - open.setAttribute("block-size", Integer.toString(this.blockSize)); - this.connected = true; - this.account.getXmppConnection().sendIqPacket(iq, - new OnIqPacketReceived() { - - @Override - public void onIqPacketReceived(Account account, - IqPacket packet) { - if (packet.getType() != IqPacket.TYPE.RESULT) { - callback.failed(); - } else { - callback.established(); - } - } - }); - } - - @Override - public void receive(DownloadableFile file, - OnFileTransmissionStatusChanged callback) { - this.onFileTransmissionStatusChanged = callback; - this.file = file; - try { - this.digest = MessageDigest.getInstance("SHA-1"); - digest.reset(); - file.getParentFile().mkdirs(); - file.createNewFile(); - this.fileOutputStream = connection.getFileOutputStream(); - if (this.fileOutputStream == null) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not create output stream"); - callback.onFileTransferAborted(); - return; - } - this.remainingSize = this.fileSize = file.getExpectedSize(); - } catch (final NoSuchAlgorithmException | IOException e) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+" "+e.getMessage()); - callback.onFileTransferAborted(); - } + private Account account; + private Jid counterpart; + private int blockSize; + private int seq = 0; + private String sessionId; + + private boolean established = false; + + private boolean connected = true; + + private DownloadableFile file; + private JingleConnection connection; + + private InputStream fileInputStream = null; + private OutputStream fileOutputStream = null; + private long remainingSize = 0; + private long fileSize = 0; + private MessageDigest digest; + + private OnFileTransmissionStatusChanged onFileTransmissionStatusChanged; + + private OnIqPacketReceived onAckReceived = new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (connected && packet.getType() == IqPacket.TYPE.RESULT) { + sendNextBlock(); + } + } + }; + + public JingleInbandTransport(final JingleConnection connection, final String sid, final int blocksize) { + this.connection = connection; + this.account = connection.getAccount(); + this.counterpart = connection.getCounterPart(); + this.blockSize = blocksize; + this.sessionId = sid; } - @Override - public void send(DownloadableFile file, - OnFileTransmissionStatusChanged callback) { - this.onFileTransmissionStatusChanged = callback; - this.file = file; - try { - this.remainingSize = this.file.getExpectedSize(); - this.fileSize = this.remainingSize; - this.digest = MessageDigest.getInstance("SHA-1"); - this.digest.reset(); - fileInputStream = connection.getFileInputStream(); - if (fileInputStream == null) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could no create input stream"); - callback.onFileTransferAborted(); - return; - } - if (this.connected) { - this.sendNextBlock(); - } - } catch (NoSuchAlgorithmException e) { - callback.onFileTransferAborted(); - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": "+e.getMessage()); - } - } - - @Override - public void disconnect() { - this.connected = false; - if (this.fileOutputStream != null) { - try { - this.fileOutputStream.close(); - } catch (IOException e) { - - } - } - if (this.fileInputStream != null) { - try { - this.fileInputStream.close(); - } catch (IOException e) { - - } - } - } - - private void sendNextBlock() { - byte[] buffer = new byte[this.blockSize]; - try { - int count = fileInputStream.read(buffer); - if (count == -1) { - file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest())); - this.onFileTransmissionStatusChanged.onFileTransmitted(file); - fileInputStream.close(); - return; - } else if (count != buffer.length) { - int rem = fileInputStream.read(buffer,count,buffer.length-count); - if (rem > 0) { - count += rem; - } - } - this.remainingSize -= count; - this.digest.update(buffer,0,count); - String base64 = Base64.encodeToString(buffer,0,count, Base64.NO_WRAP); - IqPacket iq = new IqPacket(IqPacket.TYPE.SET); - iq.setTo(this.counterpart); - Element data = iq.addChild("data", "http://jabber.org/protocol/ibb"); - data.setAttribute("seq", Integer.toString(this.seq)); - data.setAttribute("block-size", Integer.toString(this.blockSize)); - data.setAttribute("sid", this.sessionId); - data.setContent(base64); - this.account.getXmppConnection().sendIqPacket(iq, this.onAckReceived); - this.account.getXmppConnection().r(); //don't fill up stanza queue too much - this.seq++; - if (this.remainingSize > 0) { - connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100)); - } else { - file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest())); - this.onFileTransmissionStatusChanged.onFileTransmitted(file); - fileInputStream.close(); - } - } catch (IOException e) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": "+e.getMessage()); - FileBackend.close(fileInputStream); - this.onFileTransmissionStatusChanged.onFileTransferAborted(); - } - } - - private void receiveNextBlock(String data) { - try { - byte[] buffer = Base64.decode(data, Base64.NO_WRAP); - if (this.remainingSize < buffer.length) { - buffer = Arrays.copyOfRange(buffer, 0, (int) this.remainingSize); - } - this.remainingSize -= buffer.length; - this.fileOutputStream.write(buffer); - this.digest.update(buffer); - if (this.remainingSize <= 0) { - file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest())); - fileOutputStream.flush(); - fileOutputStream.close(); - this.onFileTransmissionStatusChanged.onFileTransmitted(file); - } else { - connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100)); - } - } catch (Exception e) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": "+e.getMessage()); - FileBackend.close(fileOutputStream); - this.onFileTransmissionStatusChanged.onFileTransferAborted(); - } - } - - public void deliverPayload(IqPacket packet, Element payload) { - if (payload.getName().equals("open")) { - if (!established) { - established = true; - connected = true; - this.receiveNextBlock(""); - this.account.getXmppConnection().sendIqPacket( - packet.generateResponse(IqPacket.TYPE.RESULT), null); - } else { - this.account.getXmppConnection().sendIqPacket( - packet.generateResponse(IqPacket.TYPE.ERROR), null); - } - } else if (connected && payload.getName().equals("data")) { - this.receiveNextBlock(payload.getContent()); - this.account.getXmppConnection().sendIqPacket( - packet.generateResponse(IqPacket.TYPE.RESULT), null); - } else { - // TODO some sort of exception - } - } + public void connect(final OnTransportConnected callback) { + IqPacket iq = new IqPacket(IqPacket.TYPE.SET); + iq.setTo(this.counterpart); + Element open = iq.addChild("open", "http://jabber.org/protocol/ibb"); + open.setAttribute("sid", this.sessionId); + open.setAttribute("stanza", "iq"); + open.setAttribute("block-size", Integer.toString(this.blockSize)); + this.connected = true; + this.account.getXmppConnection().sendIqPacket(iq, + new OnIqPacketReceived() { + + @Override + public void onIqPacketReceived(Account account, + IqPacket packet) { + if (packet.getType() != IqPacket.TYPE.RESULT) { + callback.failed(); + } else { + callback.established(); + } + } + }); + } + + @Override + public void receive(DownloadableFile file, + OnFileTransmissionStatusChanged callback) { + this.onFileTransmissionStatusChanged = callback; + this.file = file; + try { + this.digest = MessageDigest.getInstance("SHA-1"); + digest.reset(); + file.getParentFile().mkdirs(); + file.createNewFile(); + this.fileOutputStream = connection.getFileOutputStream(); + if (this.fileOutputStream == null) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not create output stream"); + callback.onFileTransferAborted(); + return; + } + this.remainingSize = this.fileSize = file.getExpectedSize(); + } catch (final NoSuchAlgorithmException | IOException e) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + " " + e.getMessage()); + callback.onFileTransferAborted(); + } + } + + @Override + public void send(DownloadableFile file, + OnFileTransmissionStatusChanged callback) { + this.onFileTransmissionStatusChanged = callback; + this.file = file; + try { + this.remainingSize = this.file.getExpectedSize(); + this.fileSize = this.remainingSize; + this.digest = MessageDigest.getInstance("SHA-1"); + this.digest.reset(); + fileInputStream = connection.getFileInputStream(); + if (fileInputStream == null) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could no create input stream"); + callback.onFileTransferAborted(); + return; + } + if (this.connected) { + this.sendNextBlock(); + } + } catch (NoSuchAlgorithmException e) { + callback.onFileTransferAborted(); + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": " + e.getMessage()); + } + } + + @Override + public void disconnect() { + this.connected = false; + if (this.fileOutputStream != null) { + try { + this.fileOutputStream.close(); + } catch (IOException e) { + + } + } + if (this.fileInputStream != null) { + try { + this.fileInputStream.close(); + } catch (IOException e) { + + } + } + } + + private void sendNextBlock() { + byte[] buffer = new byte[this.blockSize]; + try { + int count = fileInputStream.read(buffer); + if (count == -1) { + file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest())); + this.onFileTransmissionStatusChanged.onFileTransmitted(file); + fileInputStream.close(); + return; + } else if (count != buffer.length) { + int rem = fileInputStream.read(buffer, count, buffer.length - count); + if (rem > 0) { + count += rem; + } + } + this.remainingSize -= count; + this.digest.update(buffer, 0, count); + String base64 = Base64.encodeToString(buffer, 0, count, Base64.NO_WRAP); + IqPacket iq = new IqPacket(IqPacket.TYPE.SET); + iq.setTo(this.counterpart); + Element data = iq.addChild("data", "http://jabber.org/protocol/ibb"); + data.setAttribute("seq", Integer.toString(this.seq)); + data.setAttribute("block-size", Integer.toString(this.blockSize)); + data.setAttribute("sid", this.sessionId); + data.setContent(base64); + this.account.getXmppConnection().sendIqPacket(iq, this.onAckReceived); + this.account.getXmppConnection().r(); //don't fill up stanza queue too much + this.seq++; + if (this.remainingSize > 0) { + connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100)); + } else { + file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest())); + this.onFileTransmissionStatusChanged.onFileTransmitted(file); + fileInputStream.close(); + } + } catch (IOException e) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": " + e.getMessage()); + FileBackend.close(fileInputStream); + this.onFileTransmissionStatusChanged.onFileTransferAborted(); + } + } + + private void receiveNextBlock(String data) { + try { + byte[] buffer = Base64.decode(data, Base64.NO_WRAP); + if (this.remainingSize < buffer.length) { + buffer = Arrays.copyOfRange(buffer, 0, (int) this.remainingSize); + } + this.remainingSize -= buffer.length; + this.fileOutputStream.write(buffer); + this.digest.update(buffer); + if (this.remainingSize <= 0) { + file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest())); + fileOutputStream.flush(); + fileOutputStream.close(); + this.onFileTransmissionStatusChanged.onFileTransmitted(file); + } else { + connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100)); + } + } catch (Exception e) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": " + e.getMessage()); + FileBackend.close(fileOutputStream); + this.onFileTransmissionStatusChanged.onFileTransferAborted(); + } + } + + public void deliverPayload(IqPacket packet, Element payload) { + if (payload.getName().equals("open")) { + if (!established) { + established = true; + connected = true; + this.receiveNextBlock(""); + this.account.getXmppConnection().sendIqPacket( + packet.generateResponse(IqPacket.TYPE.RESULT), null); + } else { + this.account.getXmppConnection().sendIqPacket( + packet.generateResponse(IqPacket.TYPE.ERROR), null); + } + } else if (connected && payload.getName().equals("data")) { + this.receiveNextBlock(payload.getContent()); + this.account.getXmppConnection().sendIqPacket( + packet.generateResponse(IqPacket.TYPE.RESULT), null); + } else { + // TODO some sort of exception + } + } } diff --git a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleSocks5Transport.java b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleSocks5Transport.java index 4f3e78394..57d1afeba 100644 --- a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleSocks5Transport.java +++ b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleSocks5Transport.java @@ -20,191 +20,191 @@ import de.pixart.messenger.utils.SocksSocketFactory; import de.pixart.messenger.xmpp.jingle.stanzas.Content; public class JingleSocks5Transport extends JingleTransport { - private JingleCandidate candidate; - private JingleConnection connection; - private String destination; - private OutputStream outputStream; - private InputStream inputStream; - private boolean isEstablished = false; - private boolean activated = false; - protected Socket socket; - - public JingleSocks5Transport(JingleConnection jingleConnection, - JingleCandidate candidate) { - this.candidate = candidate; - this.connection = jingleConnection; - try { - MessageDigest mDigest = MessageDigest.getInstance("SHA-1"); - StringBuilder destBuilder = new StringBuilder(); - if (jingleConnection.getFtVersion() == Content.Version.FT_3) { - Log.d(Config.LOGTAG,this.connection.getAccount().getJid().toBareJid()+": using session Id instead of transport Id for proxy destination"); - destBuilder.append(jingleConnection.getSessionId()); - } else { - destBuilder.append(jingleConnection.getTransportId()); - } - if (candidate.isOurs()) { - destBuilder.append(jingleConnection.getAccount().getJid()); - destBuilder.append(jingleConnection.getCounterPart()); - } else { - destBuilder.append(jingleConnection.getCounterPart()); - destBuilder.append(jingleConnection.getAccount().getJid()); - } - mDigest.reset(); - this.destination = CryptoHelper.bytesToHex(mDigest - .digest(destBuilder.toString().getBytes())); - } catch (NoSuchAlgorithmException e) { - - } - } - - public void connect(final OnTransportConnected callback) { - new Thread(new Runnable() { - - @Override - public void run() { - try { - final boolean useTor = connection.getAccount().isOnion() || connection.getConnectionManager().getXmppConnectionService().useTorToConnect(); - if (useTor) { - socket = SocksSocketFactory.createSocketOverTor(candidate.getHost(),candidate.getPort()); - } else { - socket = new Socket(); - SocketAddress address = new InetSocketAddress(candidate.getHost(),candidate.getPort()); - socket.connect(address,Config.SOCKET_TIMEOUT * 1000); - } - inputStream = socket.getInputStream(); - outputStream = socket.getOutputStream(); - SocksSocketFactory.createSocksConnection(socket,destination,0); - isEstablished = true; - callback.established(); - } catch (IOException e) { - callback.failed(); - } - } - }).start(); - - } - - public void send(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) { - new Thread(new Runnable() { - - @Override - public void run() { - InputStream fileInputStream = null; - final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_send_"+connection.getSessionId()); - try { - wakeLock.acquire(); - MessageDigest digest = MessageDigest.getInstance("SHA-1"); - digest.reset(); - fileInputStream = connection.getFileInputStream(); - if (fileInputStream == null) { - Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create input stream"); - callback.onFileTransferAborted(); - return; - } - long size = file.getExpectedSize(); - long transmitted = 0; - int count; - byte[] buffer = new byte[8192]; - while ((count = fileInputStream.read(buffer)) > 0) { - outputStream.write(buffer, 0, count); - digest.update(buffer, 0, count); - transmitted += count; - connection.updateProgress((int) ((((double) transmitted) / size) * 100)); - } - outputStream.flush(); - file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest())); - if (callback != null) { - callback.onFileTransmitted(file); - } - } catch (Exception e) { - Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage()); - callback.onFileTransferAborted(); - } finally { - FileBackend.close(fileInputStream); - wakeLock.release(); - } - } - }).start(); - - } - - public void receive(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) { - new Thread(new Runnable() { - - @Override - public void run() { - OutputStream fileOutputStream = null; - final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_receive_"+connection.getSessionId()); - try { - wakeLock.acquire(); - MessageDigest digest = MessageDigest.getInstance("SHA-1"); - digest.reset(); - //inputStream.skip(45); - socket.setSoTimeout(30000); - file.getParentFile().mkdirs(); - file.createNewFile(); - fileOutputStream = connection.getFileOutputStream(); - if (fileOutputStream == null) { - callback.onFileTransferAborted(); - Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create output stream"); - return; - } - double size = file.getExpectedSize(); - long remainingSize = file.getExpectedSize(); - byte[] buffer = new byte[8192]; - int count; - while (remainingSize > 0) { - count = inputStream.read(buffer); - if (count == -1) { - callback.onFileTransferAborted(); - Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": file ended prematurely with "+remainingSize+" bytes remaining"); - return; - } else { - fileOutputStream.write(buffer, 0, count); - digest.update(buffer, 0, count); - remainingSize -= count; - } - connection.updateProgress((int) (((size - remainingSize) / size) * 100)); - } - fileOutputStream.flush(); - fileOutputStream.close(); - file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest())); - callback.onFileTransmitted(file); - } catch (Exception e) { - Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage()); - callback.onFileTransferAborted(); - } finally { - wakeLock.release(); - FileBackend.close(fileOutputStream); - FileBackend.close(inputStream); - } - } - }).start(); - } - - public boolean isProxy() { - return this.candidate.getType() == JingleCandidate.TYPE_PROXY; - } - - public boolean needsActivation() { - return (this.isProxy() && !this.activated); - } - - public void disconnect() { - FileBackend.close(inputStream); - FileBackend.close(outputStream); - FileBackend.close(socket); - } - - public boolean isEstablished() { - return this.isEstablished; - } - - public JingleCandidate getCandidate() { - return this.candidate; - } - - public void setActivated(boolean activated) { - this.activated = activated; - } + private JingleCandidate candidate; + private JingleConnection connection; + private String destination; + private OutputStream outputStream; + private InputStream inputStream; + private boolean isEstablished = false; + private boolean activated = false; + protected Socket socket; + + public JingleSocks5Transport(JingleConnection jingleConnection, + JingleCandidate candidate) { + this.candidate = candidate; + this.connection = jingleConnection; + try { + MessageDigest mDigest = MessageDigest.getInstance("SHA-1"); + StringBuilder destBuilder = new StringBuilder(); + if (jingleConnection.getFtVersion() == Content.Version.FT_3) { + Log.d(Config.LOGTAG, this.connection.getAccount().getJid().toBareJid() + ": using session Id instead of transport Id for proxy destination"); + destBuilder.append(jingleConnection.getSessionId()); + } else { + destBuilder.append(jingleConnection.getTransportId()); + } + if (candidate.isOurs()) { + destBuilder.append(jingleConnection.getAccount().getJid()); + destBuilder.append(jingleConnection.getCounterPart()); + } else { + destBuilder.append(jingleConnection.getCounterPart()); + destBuilder.append(jingleConnection.getAccount().getJid()); + } + mDigest.reset(); + this.destination = CryptoHelper.bytesToHex(mDigest + .digest(destBuilder.toString().getBytes())); + } catch (NoSuchAlgorithmException e) { + + } + } + + public void connect(final OnTransportConnected callback) { + new Thread(new Runnable() { + + @Override + public void run() { + try { + final boolean useTor = connection.getAccount().isOnion() || connection.getConnectionManager().getXmppConnectionService().useTorToConnect(); + if (useTor) { + socket = SocksSocketFactory.createSocketOverTor(candidate.getHost(), candidate.getPort()); + } else { + socket = new Socket(); + SocketAddress address = new InetSocketAddress(candidate.getHost(), candidate.getPort()); + socket.connect(address, Config.SOCKET_TIMEOUT * 1000); + } + inputStream = socket.getInputStream(); + outputStream = socket.getOutputStream(); + SocksSocketFactory.createSocksConnection(socket, destination, 0); + isEstablished = true; + callback.established(); + } catch (IOException e) { + callback.failed(); + } + } + }).start(); + + } + + public void send(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) { + new Thread(new Runnable() { + + @Override + public void run() { + InputStream fileInputStream = null; + final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_send_" + connection.getSessionId()); + try { + wakeLock.acquire(); + MessageDigest digest = MessageDigest.getInstance("SHA-1"); + digest.reset(); + fileInputStream = connection.getFileInputStream(); + if (fileInputStream == null) { + Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create input stream"); + callback.onFileTransferAborted(); + return; + } + long size = file.getExpectedSize(); + long transmitted = 0; + int count; + byte[] buffer = new byte[8192]; + while ((count = fileInputStream.read(buffer)) > 0) { + outputStream.write(buffer, 0, count); + digest.update(buffer, 0, count); + transmitted += count; + connection.updateProgress((int) ((((double) transmitted) / size) * 100)); + } + outputStream.flush(); + file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest())); + if (callback != null) { + callback.onFileTransmitted(file); + } + } catch (Exception e) { + Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": " + e.getMessage()); + callback.onFileTransferAborted(); + } finally { + FileBackend.close(fileInputStream); + wakeLock.release(); + } + } + }).start(); + + } + + public void receive(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) { + new Thread(new Runnable() { + + @Override + public void run() { + OutputStream fileOutputStream = null; + final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_receive_" + connection.getSessionId()); + try { + wakeLock.acquire(); + MessageDigest digest = MessageDigest.getInstance("SHA-1"); + digest.reset(); + //inputStream.skip(45); + socket.setSoTimeout(30000); + file.getParentFile().mkdirs(); + file.createNewFile(); + fileOutputStream = connection.getFileOutputStream(); + if (fileOutputStream == null) { + callback.onFileTransferAborted(); + Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create output stream"); + return; + } + double size = file.getExpectedSize(); + long remainingSize = file.getExpectedSize(); + byte[] buffer = new byte[8192]; + int count; + while (remainingSize > 0) { + count = inputStream.read(buffer); + if (count == -1) { + callback.onFileTransferAborted(); + Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": file ended prematurely with " + remainingSize + " bytes remaining"); + return; + } else { + fileOutputStream.write(buffer, 0, count); + digest.update(buffer, 0, count); + remainingSize -= count; + } + connection.updateProgress((int) (((size - remainingSize) / size) * 100)); + } + fileOutputStream.flush(); + fileOutputStream.close(); + file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest())); + callback.onFileTransmitted(file); + } catch (Exception e) { + Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": " + e.getMessage()); + callback.onFileTransferAborted(); + } finally { + wakeLock.release(); + FileBackend.close(fileOutputStream); + FileBackend.close(inputStream); + } + } + }).start(); + } + + public boolean isProxy() { + return this.candidate.getType() == JingleCandidate.TYPE_PROXY; + } + + public boolean needsActivation() { + return (this.isProxy() && !this.activated); + } + + public void disconnect() { + FileBackend.close(inputStream); + FileBackend.close(outputStream); + FileBackend.close(socket); + } + + public boolean isEstablished() { + return this.isEstablished; + } + + public JingleCandidate getCandidate() { + return this.candidate; + } + + public void setActivated(boolean activated) { + this.activated = activated; + } } diff --git a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleTransport.java b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleTransport.java index 1ee97b415..b9adb4632 100644 --- a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleTransport.java +++ b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleTransport.java @@ -3,13 +3,13 @@ package de.pixart.messenger.xmpp.jingle; import de.pixart.messenger.entities.DownloadableFile; public abstract class JingleTransport { - public abstract void connect(final OnTransportConnected callback); + public abstract void connect(final OnTransportConnected callback); - public abstract void receive(final DownloadableFile file, - final OnFileTransmissionStatusChanged callback); + public abstract void receive(final DownloadableFile file, + final OnFileTransmissionStatusChanged callback); - public abstract void send(final DownloadableFile file, - final OnFileTransmissionStatusChanged callback); + public abstract void send(final DownloadableFile file, + final OnFileTransmissionStatusChanged callback); - public abstract void disconnect(); + public abstract void disconnect(); } diff --git a/src/main/java/de/pixart/messenger/xmpp/jingle/OnFileTransmissionStatusChanged.java b/src/main/java/de/pixart/messenger/xmpp/jingle/OnFileTransmissionStatusChanged.java index 6bbf7111a..65f9726d8 100644 --- a/src/main/java/de/pixart/messenger/xmpp/jingle/OnFileTransmissionStatusChanged.java +++ b/src/main/java/de/pixart/messenger/xmpp/jingle/OnFileTransmissionStatusChanged.java @@ -3,7 +3,7 @@ package de.pixart.messenger.xmpp.jingle; import de.pixart.messenger.entities.DownloadableFile; public interface OnFileTransmissionStatusChanged { - void onFileTransmitted(DownloadableFile file); + void onFileTransmitted(DownloadableFile file); - void onFileTransferAborted(); + void onFileTransferAborted(); } diff --git a/src/main/java/de/pixart/messenger/xmpp/jingle/OnJinglePacketReceived.java b/src/main/java/de/pixart/messenger/xmpp/jingle/OnJinglePacketReceived.java index 82dfefa5e..4cacac74c 100644 --- a/src/main/java/de/pixart/messenger/xmpp/jingle/OnJinglePacketReceived.java +++ b/src/main/java/de/pixart/messenger/xmpp/jingle/OnJinglePacketReceived.java @@ -5,5 +5,5 @@ import de.pixart.messenger.xmpp.PacketReceived; import de.pixart.messenger.xmpp.jingle.stanzas.JinglePacket; public interface OnJinglePacketReceived extends PacketReceived { - void onJinglePacketReceived(Account account, JinglePacket packet); + void onJinglePacketReceived(Account account, JinglePacket packet); } diff --git a/src/main/java/de/pixart/messenger/xmpp/jingle/OnPrimaryCandidateFound.java b/src/main/java/de/pixart/messenger/xmpp/jingle/OnPrimaryCandidateFound.java index fad7bb4d4..b1fb0b02a 100644 --- a/src/main/java/de/pixart/messenger/xmpp/jingle/OnPrimaryCandidateFound.java +++ b/src/main/java/de/pixart/messenger/xmpp/jingle/OnPrimaryCandidateFound.java @@ -1,5 +1,5 @@ package de.pixart.messenger.xmpp.jingle; public interface OnPrimaryCandidateFound { - void onPrimaryCandidateFound(boolean success, JingleCandidate canditate); + void onPrimaryCandidateFound(boolean success, JingleCandidate canditate); } diff --git a/src/main/java/de/pixart/messenger/xmpp/jingle/OnTransportConnected.java b/src/main/java/de/pixart/messenger/xmpp/jingle/OnTransportConnected.java index 10efd76d7..11a3eed5e 100644 --- a/src/main/java/de/pixart/messenger/xmpp/jingle/OnTransportConnected.java +++ b/src/main/java/de/pixart/messenger/xmpp/jingle/OnTransportConnected.java @@ -1,7 +1,7 @@ package de.pixart.messenger.xmpp.jingle; public interface OnTransportConnected { - public void failed(); + public void failed(); - public void established(); + public void established(); } diff --git a/src/main/java/de/pixart/messenger/xmpp/jingle/stanzas/Content.java b/src/main/java/de/pixart/messenger/xmpp/jingle/stanzas/Content.java index bef32926f..fc7231e30 100644 --- a/src/main/java/de/pixart/messenger/xmpp/jingle/stanzas/Content.java +++ b/src/main/java/de/pixart/messenger/xmpp/jingle/stanzas/Content.java @@ -5,125 +5,125 @@ import de.pixart.messenger.xml.Element; public class Content extends Element { - public enum Version { - FT_3("urn:xmpp:jingle:apps:file-transfer:3"), - FT_4("urn:xmpp:jingle:apps:file-transfer:4"); - - private final String namespace; - - Version(String namespace) { - this.namespace = namespace; - } - - public String getNamespace() { - return namespace; - } - } - - private String transportId; - - public Content() { - super("content"); - } - - public Content(String creator, String name) { - super("content"); - this.setAttribute("creator", creator); - this.setAttribute("name", name); - } - - public Version getVersion() { - if (hasChild("description", Version.FT_3.namespace)) { - return Version.FT_3; - } else if (hasChild("description" , Version.FT_4.namespace)) { - return Version.FT_4; - } - return null; - } - - public void setTransportId(String sid) { - this.transportId = sid; - } - - public Element setFileOffer(DownloadableFile actualFile, boolean otr, Version version) { - Element description = this.addChild("description", version.namespace); - Element file; - if (version == Version.FT_3) { - Element offer = description.addChild("offer"); - file = offer.addChild("file"); - } else { - file = description.addChild("file"); - } - file.addChild("size").setContent(Long.toString(actualFile.getExpectedSize())); - if (otr) { - file.addChild("name").setContent(actualFile.getName() + ".otr"); - } else { - file.addChild("name").setContent(actualFile.getName()); - } - return file; - } - - public Element getFileOffer(Version version) { - Element description = this.findChild("description", version.namespace); - if (description == null) { - return null; - } - if (version == Version.FT_3) { - Element offer = description.findChild("offer"); - if (offer == null) { - return null; - } - return offer.findChild("file"); - } else { - return description.findChild("file"); - } - } - - public void setFileOffer(Element fileOffer, Version version) { - Element description = this.addChild("description", version.namespace); - if (version == Version.FT_3) { - description.addChild("offer").addChild(fileOffer); - } else { - description.addChild(fileOffer); - } - } - - public String getTransportId() { - if (hasSocks5Transport()) { - this.transportId = socks5transport().getAttribute("sid"); - } else if (hasIbbTransport()) { - this.transportId = ibbTransport().getAttribute("sid"); - } - return this.transportId; - } - - public Element socks5transport() { - Element transport = this.findChild("transport", - "urn:xmpp:jingle:transports:s5b:1"); - if (transport == null) { - transport = this.addChild("transport", - "urn:xmpp:jingle:transports:s5b:1"); - transport.setAttribute("sid", this.transportId); - } - return transport; - } - - public Element ibbTransport() { - Element transport = this.findChild("transport", - "urn:xmpp:jingle:transports:ibb:1"); - if (transport == null) { - transport = this.addChild("transport", - "urn:xmpp:jingle:transports:ibb:1"); - transport.setAttribute("sid", this.transportId); - } - return transport; - } - - public boolean hasSocks5Transport() { - return this.hasChild("transport", "urn:xmpp:jingle:transports:s5b:1"); - } - - public boolean hasIbbTransport() { - return this.hasChild("transport", "urn:xmpp:jingle:transports:ibb:1"); - } + public enum Version { + FT_3("urn:xmpp:jingle:apps:file-transfer:3"), + FT_4("urn:xmpp:jingle:apps:file-transfer:4"); + + private final String namespace; + + Version(String namespace) { + this.namespace = namespace; + } + + public String getNamespace() { + return namespace; + } + } + + private String transportId; + + public Content() { + super("content"); + } + + public Content(String creator, String name) { + super("content"); + this.setAttribute("creator", creator); + this.setAttribute("name", name); + } + + public Version getVersion() { + if (hasChild("description", Version.FT_3.namespace)) { + return Version.FT_3; + } else if (hasChild("description", Version.FT_4.namespace)) { + return Version.FT_4; + } + return null; + } + + public void setTransportId(String sid) { + this.transportId = sid; + } + + public Element setFileOffer(DownloadableFile actualFile, boolean otr, Version version) { + Element description = this.addChild("description", version.namespace); + Element file; + if (version == Version.FT_3) { + Element offer = description.addChild("offer"); + file = offer.addChild("file"); + } else { + file = description.addChild("file"); + } + file.addChild("size").setContent(Long.toString(actualFile.getExpectedSize())); + if (otr) { + file.addChild("name").setContent(actualFile.getName() + ".otr"); + } else { + file.addChild("name").setContent(actualFile.getName()); + } + return file; + } + + public Element getFileOffer(Version version) { + Element description = this.findChild("description", version.namespace); + if (description == null) { + return null; + } + if (version == Version.FT_3) { + Element offer = description.findChild("offer"); + if (offer == null) { + return null; + } + return offer.findChild("file"); + } else { + return description.findChild("file"); + } + } + + public void setFileOffer(Element fileOffer, Version version) { + Element description = this.addChild("description", version.namespace); + if (version == Version.FT_3) { + description.addChild("offer").addChild(fileOffer); + } else { + description.addChild(fileOffer); + } + } + + public String getTransportId() { + if (hasSocks5Transport()) { + this.transportId = socks5transport().getAttribute("sid"); + } else if (hasIbbTransport()) { + this.transportId = ibbTransport().getAttribute("sid"); + } + return this.transportId; + } + + public Element socks5transport() { + Element transport = this.findChild("transport", + "urn:xmpp:jingle:transports:s5b:1"); + if (transport == null) { + transport = this.addChild("transport", + "urn:xmpp:jingle:transports:s5b:1"); + transport.setAttribute("sid", this.transportId); + } + return transport; + } + + public Element ibbTransport() { + Element transport = this.findChild("transport", + "urn:xmpp:jingle:transports:ibb:1"); + if (transport == null) { + transport = this.addChild("transport", + "urn:xmpp:jingle:transports:ibb:1"); + transport.setAttribute("sid", this.transportId); + } + return transport; + } + + public boolean hasSocks5Transport() { + return this.hasChild("transport", "urn:xmpp:jingle:transports:s5b:1"); + } + + public boolean hasIbbTransport() { + return this.hasChild("transport", "urn:xmpp:jingle:transports:ibb:1"); + } } diff --git a/src/main/java/de/pixart/messenger/xmpp/jingle/stanzas/JinglePacket.java b/src/main/java/de/pixart/messenger/xmpp/jingle/stanzas/JinglePacket.java index 971ab8e1b..de856772a 100644 --- a/src/main/java/de/pixart/messenger/xmpp/jingle/stanzas/JinglePacket.java +++ b/src/main/java/de/pixart/messenger/xmpp/jingle/stanzas/JinglePacket.java @@ -5,92 +5,92 @@ import de.pixart.messenger.xmpp.jid.Jid; import de.pixart.messenger.xmpp.stanzas.IqPacket; public class JinglePacket extends IqPacket { - Content content = null; - Reason reason = null; - Element jingle = new Element("jingle"); + Content content = null; + Reason reason = null; + Element jingle = new Element("jingle"); - @Override - public Element addChild(Element child) { - if ("jingle".equals(child.getName())) { - Element contentElement = child.findChild("content"); - if (contentElement != null) { - this.content = new Content(); - this.content.setChildren(contentElement.getChildren()); - this.content.setAttributes(contentElement.getAttributes()); - } - Element reasonElement = child.findChild("reason"); - if (reasonElement != null) { - this.reason = new Reason(); - this.reason.setChildren(reasonElement.getChildren()); - this.reason.setAttributes(reasonElement.getAttributes()); - } - this.jingle.setAttributes(child.getAttributes()); - } - return child; - } + @Override + public Element addChild(Element child) { + if ("jingle".equals(child.getName())) { + Element contentElement = child.findChild("content"); + if (contentElement != null) { + this.content = new Content(); + this.content.setChildren(contentElement.getChildren()); + this.content.setAttributes(contentElement.getAttributes()); + } + Element reasonElement = child.findChild("reason"); + if (reasonElement != null) { + this.reason = new Reason(); + this.reason.setChildren(reasonElement.getChildren()); + this.reason.setAttributes(reasonElement.getAttributes()); + } + this.jingle.setAttributes(child.getAttributes()); + } + return child; + } - public JinglePacket setContent(Content content) { - this.content = content; - return this; - } + public JinglePacket setContent(Content content) { + this.content = content; + return this; + } - public Content getJingleContent() { - if (this.content == null) { - this.content = new Content(); - } - return this.content; - } + public Content getJingleContent() { + if (this.content == null) { + this.content = new Content(); + } + return this.content; + } - public JinglePacket setReason(Reason reason) { - this.reason = reason; - return this; - } + public JinglePacket setReason(Reason reason) { + this.reason = reason; + return this; + } - public Reason getReason() { - return this.reason; - } + public Reason getReason() { + return this.reason; + } - private void build() { - this.children.clear(); - this.jingle.clearChildren(); - this.jingle.setAttribute("xmlns", "urn:xmpp:jingle:1"); - if (this.content != null) { - jingle.addChild(this.content); - } - if (this.reason != null) { - jingle.addChild(this.reason); - } - this.children.add(jingle); - this.setAttribute("type", "set"); - } + private void build() { + this.children.clear(); + this.jingle.clearChildren(); + this.jingle.setAttribute("xmlns", "urn:xmpp:jingle:1"); + if (this.content != null) { + jingle.addChild(this.content); + } + if (this.reason != null) { + jingle.addChild(this.reason); + } + this.children.add(jingle); + this.setAttribute("type", "set"); + } - public String getSessionId() { - return this.jingle.getAttribute("sid"); - } + public String getSessionId() { + return this.jingle.getAttribute("sid"); + } - public void setSessionId(String sid) { - this.jingle.setAttribute("sid", sid); - } + public void setSessionId(String sid) { + this.jingle.setAttribute("sid", sid); + } - @Override - public String toString() { - this.build(); - return super.toString(); - } + @Override + public String toString() { + this.build(); + return super.toString(); + } - public void setAction(String action) { - this.jingle.setAttribute("action", action); - } + public void setAction(String action) { + this.jingle.setAttribute("action", action); + } - public String getAction() { - return this.jingle.getAttribute("action"); - } + public String getAction() { + return this.jingle.getAttribute("action"); + } - public void setInitiator(final Jid initiator) { - this.jingle.setAttribute("initiator", initiator.toString()); - } + public void setInitiator(final Jid initiator) { + this.jingle.setAttribute("initiator", initiator.toString()); + } - public boolean isAction(String action) { - return action.equalsIgnoreCase(this.getAction()); - } + public boolean isAction(String action) { + return action.equalsIgnoreCase(this.getAction()); + } } diff --git a/src/main/java/de/pixart/messenger/xmpp/jingle/stanzas/Reason.java b/src/main/java/de/pixart/messenger/xmpp/jingle/stanzas/Reason.java index 3c77a91a6..19f27bc40 100644 --- a/src/main/java/de/pixart/messenger/xmpp/jingle/stanzas/Reason.java +++ b/src/main/java/de/pixart/messenger/xmpp/jingle/stanzas/Reason.java @@ -3,11 +3,11 @@ package de.pixart.messenger.xmpp.jingle.stanzas; import de.pixart.messenger.xml.Element; public class Reason extends Element { - private Reason(String name) { - super(name); - } + private Reason(String name) { + super(name); + } - public Reason() { - super("reason"); - } + public Reason() { + super("reason"); + } } diff --git a/src/main/java/de/pixart/messenger/xmpp/pep/Avatar.java b/src/main/java/de/pixart/messenger/xmpp/pep/Avatar.java index 1f28cd92d..655eabfc8 100644 --- a/src/main/java/de/pixart/messenger/xmpp/pep/Avatar.java +++ b/src/main/java/de/pixart/messenger/xmpp/pep/Avatar.java @@ -7,96 +7,98 @@ import de.pixart.messenger.xmpp.jid.Jid; public class Avatar { - public enum Origin { PEP, VCARD }; + public enum Origin {PEP, VCARD} - public String type; - public String sha1sum; - public String image; - public int height; - public int width; - public long size; - public Jid owner; - public Origin origin = Origin.PEP; //default to maintain compat + ; - public byte[] getImageAsBytes() { - return Base64.decode(image, Base64.DEFAULT); - } + public String type; + public String sha1sum; + public String image; + public int height; + public int width; + public long size; + public Jid owner; + public Origin origin = Origin.PEP; //default to maintain compat - public String getFilename() { - return sha1sum; - } + public byte[] getImageAsBytes() { + return Base64.decode(image, Base64.DEFAULT); + } - public static Avatar parseMetadata(Element items) { - Element item = items.findChild("item"); - if (item == null) { - return null; - } - Element metadata = item.findChild("metadata"); - if (metadata == null) { - return null; - } - String primaryId = item.getAttribute("id"); - if (primaryId == null) { - return null; - } - for (Element child : metadata.getChildren()) { - if (child.getName().equals("info") - && primaryId.equals(child.getAttribute("id"))) { - Avatar avatar = new Avatar(); - String height = child.getAttribute("height"); - String width = child.getAttribute("width"); - String size = child.getAttribute("bytes"); - try { - if (height != null) { - avatar.height = Integer.parseInt(height); - } - if (width != null) { - avatar.width = Integer.parseInt(width); - } - if (size != null) { - avatar.size = Long.parseLong(size); - } - } catch (NumberFormatException e) { - return null; - } - avatar.type = child.getAttribute("type"); - String hash = child.getAttribute("id"); - if (!isValidSHA1(hash)) { - return null; - } - avatar.sha1sum = hash; - avatar.origin = Origin.PEP; - return avatar; - } - } - return null; - } + public String getFilename() { + return sha1sum; + } - @Override - public boolean equals(Object object) { - if (object != null && object instanceof Avatar) { - Avatar other = (Avatar) object; - return other.getFilename().equals(this.getFilename()); - } else { - return false; - } - } + public static Avatar parseMetadata(Element items) { + Element item = items.findChild("item"); + if (item == null) { + return null; + } + Element metadata = item.findChild("metadata"); + if (metadata == null) { + return null; + } + String primaryId = item.getAttribute("id"); + if (primaryId == null) { + return null; + } + for (Element child : metadata.getChildren()) { + if (child.getName().equals("info") + && primaryId.equals(child.getAttribute("id"))) { + Avatar avatar = new Avatar(); + String height = child.getAttribute("height"); + String width = child.getAttribute("width"); + String size = child.getAttribute("bytes"); + try { + if (height != null) { + avatar.height = Integer.parseInt(height); + } + if (width != null) { + avatar.width = Integer.parseInt(width); + } + if (size != null) { + avatar.size = Long.parseLong(size); + } + } catch (NumberFormatException e) { + return null; + } + avatar.type = child.getAttribute("type"); + String hash = child.getAttribute("id"); + if (!isValidSHA1(hash)) { + return null; + } + avatar.sha1sum = hash; + avatar.origin = Origin.PEP; + return avatar; + } + } + return null; + } - public static Avatar parsePresence(Element x) { - String hash = x == null ? null : x.findChildContent("photo"); - if (hash == null) { - return null; - } - if (!isValidSHA1(hash)) { - return null; - } - Avatar avatar = new Avatar(); - avatar.sha1sum = hash; - avatar.origin = Origin.VCARD; - return avatar; - } + @Override + public boolean equals(Object object) { + if (object != null && object instanceof Avatar) { + Avatar other = (Avatar) object; + return other.getFilename().equals(this.getFilename()); + } else { + return false; + } + } - private static boolean isValidSHA1(String s) { - return s != null && s.matches("[a-fA-F0-9]{40}"); - } + public static Avatar parsePresence(Element x) { + String hash = x == null ? null : x.findChildContent("photo"); + if (hash == null) { + return null; + } + if (!isValidSHA1(hash)) { + return null; + } + Avatar avatar = new Avatar(); + avatar.sha1sum = hash; + avatar.origin = Origin.VCARD; + return avatar; + } + + private static boolean isValidSHA1(String s) { + return s != null && s.matches("[a-fA-F0-9]{40}"); + } } diff --git a/src/main/java/de/pixart/messenger/xmpp/stanzas/AbstractAcknowledgeableStanza.java b/src/main/java/de/pixart/messenger/xmpp/stanzas/AbstractAcknowledgeableStanza.java index 615c4dfbe..22d035e6e 100644 --- a/src/main/java/de/pixart/messenger/xmpp/stanzas/AbstractAcknowledgeableStanza.java +++ b/src/main/java/de/pixart/messenger/xmpp/stanzas/AbstractAcknowledgeableStanza.java @@ -4,28 +4,28 @@ import de.pixart.messenger.xml.Element; abstract public class AbstractAcknowledgeableStanza extends AbstractStanza { - protected AbstractAcknowledgeableStanza(String name) { - super(name); - } + protected AbstractAcknowledgeableStanza(String name) { + super(name); + } - public String getId() { - return this.getAttribute("id"); - } + public String getId() { + return this.getAttribute("id"); + } - public void setId(final String id) { - setAttribute("id", id); - } + public void setId(final String id) { + setAttribute("id", id); + } - public Element getError() { - Element error = findChild("error"); - if (error != null) { - for(Element element : error.getChildren()) { - if (!element.getName().equals("text")) { - return element; - } - } - } - return null; - } + public Element getError() { + Element error = findChild("error"); + if (error != null) { + for (Element element : error.getChildren()) { + if (!element.getName().equals("text")) { + return element; + } + } + } + return null; + } } diff --git a/src/main/java/de/pixart/messenger/xmpp/stanzas/AbstractStanza.java b/src/main/java/de/pixart/messenger/xmpp/stanzas/AbstractStanza.java index a21d8b56d..8d1b24ef8 100644 --- a/src/main/java/de/pixart/messenger/xmpp/stanzas/AbstractStanza.java +++ b/src/main/java/de/pixart/messenger/xmpp/stanzas/AbstractStanza.java @@ -6,45 +6,45 @@ import de.pixart.messenger.xmpp.jid.Jid; public class AbstractStanza extends Element { - protected AbstractStanza(final String name) { - super(name); - } - - public Jid getTo() { - return getAttributeAsJid("to"); - } - - public Jid getFrom() { - return getAttributeAsJid("from"); - } - - public void setTo(final Jid to) { - if (to != null) { - setAttribute("to", to.toString()); - } - } - - public void setFrom(final Jid from) { - if (from != null) { - setAttribute("from", from.toString()); - } - } - - public boolean fromServer(final Account account) { - return getFrom() == null - || getFrom().equals(account.getServer()) - || getFrom().equals(account.getJid().toBareJid()) - || getFrom().equals(account.getJid()); - } - - public boolean toServer(final Account account) { - return getTo() == null - || getTo().equals(account.getServer()) - || getTo().equals(account.getJid().toBareJid()) - || getTo().equals(account.getJid()); - } - - public boolean fromAccount(final Account account) { - return getFrom() != null && getFrom().toBareJid().equals(account.getJid().toBareJid()); - } + protected AbstractStanza(final String name) { + super(name); + } + + public Jid getTo() { + return getAttributeAsJid("to"); + } + + public Jid getFrom() { + return getAttributeAsJid("from"); + } + + public void setTo(final Jid to) { + if (to != null) { + setAttribute("to", to.toString()); + } + } + + public void setFrom(final Jid from) { + if (from != null) { + setAttribute("from", from.toString()); + } + } + + public boolean fromServer(final Account account) { + return getFrom() == null + || getFrom().equals(account.getServer()) + || getFrom().equals(account.getJid().toBareJid()) + || getFrom().equals(account.getJid()); + } + + public boolean toServer(final Account account) { + return getTo() == null + || getTo().equals(account.getServer()) + || getTo().equals(account.getJid().toBareJid()) + || getTo().equals(account.getJid()); + } + + public boolean fromAccount(final Account account) { + return getFrom() != null && getFrom().toBareJid().equals(account.getJid().toBareJid()); + } } diff --git a/src/main/java/de/pixart/messenger/xmpp/stanzas/IqPacket.java b/src/main/java/de/pixart/messenger/xmpp/stanzas/IqPacket.java index b0a22c314..2efc948f4 100644 --- a/src/main/java/de/pixart/messenger/xmpp/stanzas/IqPacket.java +++ b/src/main/java/de/pixart/messenger/xmpp/stanzas/IqPacket.java @@ -4,66 +4,66 @@ import de.pixart.messenger.xml.Element; public class IqPacket extends AbstractAcknowledgeableStanza { - public enum TYPE { - ERROR, - SET, - RESULT, - GET, - INVALID, - TIMEOUT - } + public enum TYPE { + ERROR, + SET, + RESULT, + GET, + INVALID, + TIMEOUT + } - public IqPacket(final TYPE type) { - super("iq"); - if (type != TYPE.INVALID) { - this.setAttribute("type", type.toString().toLowerCase()); - } - } + public IqPacket(final TYPE type) { + super("iq"); + if (type != TYPE.INVALID) { + this.setAttribute("type", type.toString().toLowerCase()); + } + } - public IqPacket() { - super("iq"); - } + public IqPacket() { + super("iq"); + } - public Element query() { - Element query = findChild("query"); - if (query == null) { - query = addChild("query"); - } - return query; - } + public Element query() { + Element query = findChild("query"); + if (query == null) { + query = addChild("query"); + } + return query; + } - public Element query(final String xmlns) { - final Element query = query(); - query.setAttribute("xmlns", xmlns); - return query(); - } + public Element query(final String xmlns) { + final Element query = query(); + query.setAttribute("xmlns", xmlns); + return query(); + } - public TYPE getType() { - final String type = getAttribute("type"); - if (type == null) { - return TYPE.INVALID; - } - switch (type) { - case "error": - return TYPE.ERROR; - case "result": - return TYPE.RESULT; - case "set": - return TYPE.SET; - case "get": - return TYPE.GET; - case "timeout": - return TYPE.TIMEOUT; - default: - return TYPE.INVALID; - } - } + public TYPE getType() { + final String type = getAttribute("type"); + if (type == null) { + return TYPE.INVALID; + } + switch (type) { + case "error": + return TYPE.ERROR; + case "result": + return TYPE.RESULT; + case "set": + return TYPE.SET; + case "get": + return TYPE.GET; + case "timeout": + return TYPE.TIMEOUT; + default: + return TYPE.INVALID; + } + } - public IqPacket generateResponse(final TYPE type) { - final IqPacket packet = new IqPacket(type); - packet.setTo(this.getFrom()); - packet.setId(this.getId()); - return packet; - } + public IqPacket generateResponse(final TYPE type) { + final IqPacket packet = new IqPacket(type); + packet.setTo(this.getFrom()); + packet.setId(this.getId()); + return packet; + } } diff --git a/src/main/java/de/pixart/messenger/xmpp/stanzas/MessagePacket.java b/src/main/java/de/pixart/messenger/xmpp/stanzas/MessagePacket.java index 5af2d701b..2f7d7e071 100644 --- a/src/main/java/de/pixart/messenger/xmpp/stanzas/MessagePacket.java +++ b/src/main/java/de/pixart/messenger/xmpp/stanzas/MessagePacket.java @@ -6,94 +6,94 @@ import de.pixart.messenger.parser.AbstractParser; import de.pixart.messenger.xml.Element; public class MessagePacket extends AbstractAcknowledgeableStanza { - public static final int TYPE_CHAT = 0; - public static final int TYPE_NORMAL = 2; - public static final int TYPE_GROUPCHAT = 3; - public static final int TYPE_ERROR = 4; - public static final int TYPE_HEADLINE = 5; + public static final int TYPE_CHAT = 0; + public static final int TYPE_NORMAL = 2; + public static final int TYPE_GROUPCHAT = 3; + public static final int TYPE_ERROR = 4; + public static final int TYPE_HEADLINE = 5; - public MessagePacket() { - super("message"); - } + public MessagePacket() { + super("message"); + } - public String getBody() { - return findChildContent("body"); - } + public String getBody() { + return findChildContent("body"); + } - public void setBody(String text) { - this.children.remove(findChild("body")); - Element body = new Element("body"); - body.setContent(text); - this.children.add(0, body); - } + public void setBody(String text) { + this.children.remove(findChild("body")); + Element body = new Element("body"); + body.setContent(text); + this.children.add(0, body); + } - public void setAxolotlMessage(Element axolotlMessage) { - this.children.remove(findChild("body")); - this.children.add(0, axolotlMessage); - } + public void setAxolotlMessage(Element axolotlMessage) { + this.children.remove(findChild("body")); + this.children.add(0, axolotlMessage); + } - public void setType(int type) { - switch (type) { - case TYPE_CHAT: - this.setAttribute("type", "chat"); - break; - case TYPE_GROUPCHAT: - this.setAttribute("type", "groupchat"); - break; - case TYPE_NORMAL: - break; - case TYPE_ERROR: - this.setAttribute("type","error"); - break; - default: - this.setAttribute("type", "chat"); - break; - } - } + public void setType(int type) { + switch (type) { + case TYPE_CHAT: + this.setAttribute("type", "chat"); + break; + case TYPE_GROUPCHAT: + this.setAttribute("type", "groupchat"); + break; + case TYPE_NORMAL: + break; + case TYPE_ERROR: + this.setAttribute("type", "error"); + break; + default: + this.setAttribute("type", "chat"); + break; + } + } - public int getType() { - String type = getAttribute("type"); - if (type == null) { - return TYPE_NORMAL; - } else if (type.equals("normal")) { - return TYPE_NORMAL; - } else if (type.equals("chat")) { - return TYPE_CHAT; - } else if (type.equals("groupchat")) { - return TYPE_GROUPCHAT; - } else if (type.equals("error")) { - return TYPE_ERROR; - } else if (type.equals("headline")) { - return TYPE_HEADLINE; - } else { - return TYPE_NORMAL; - } - } + public int getType() { + String type = getAttribute("type"); + if (type == null) { + return TYPE_NORMAL; + } else if (type.equals("normal")) { + return TYPE_NORMAL; + } else if (type.equals("chat")) { + return TYPE_CHAT; + } else if (type.equals("groupchat")) { + return TYPE_GROUPCHAT; + } else if (type.equals("error")) { + return TYPE_ERROR; + } else if (type.equals("headline")) { + return TYPE_HEADLINE; + } else { + return TYPE_NORMAL; + } + } - public Pair getForwardedMessagePacket(String name, String namespace) { - Element wrapper = findChild(name, namespace); - if (wrapper == null) { - return null; - } - Element forwarded = wrapper.findChild("forwarded", "urn:xmpp:forward:0"); - if (forwarded == null) { - return null; - } - MessagePacket packet = create(forwarded.findChild("message")); - if (packet == null) { - return null; - } - Long timestamp = AbstractParser.parseTimestamp(forwarded, null); - return new Pair(packet,timestamp); - } + public Pair getForwardedMessagePacket(String name, String namespace) { + Element wrapper = findChild(name, namespace); + if (wrapper == null) { + return null; + } + Element forwarded = wrapper.findChild("forwarded", "urn:xmpp:forward:0"); + if (forwarded == null) { + return null; + } + MessagePacket packet = create(forwarded.findChild("message")); + if (packet == null) { + return null; + } + Long timestamp = AbstractParser.parseTimestamp(forwarded, null); + return new Pair(packet, timestamp); + } - public static MessagePacket create(Element element) { - if (element == null) { - return null; - } - MessagePacket packet = new MessagePacket(); - packet.setAttributes(element.getAttributes()); - packet.setChildren(element.getChildren()); - return packet; - } + public static MessagePacket create(Element element) { + if (element == null) { + return null; + } + MessagePacket packet = new MessagePacket(); + packet.setAttributes(element.getAttributes()); + packet.setChildren(element.getChildren()); + return packet; + } } diff --git a/src/main/java/de/pixart/messenger/xmpp/stanzas/PresencePacket.java b/src/main/java/de/pixart/messenger/xmpp/stanzas/PresencePacket.java index 6c816cac3..6c57de807 100644 --- a/src/main/java/de/pixart/messenger/xmpp/stanzas/PresencePacket.java +++ b/src/main/java/de/pixart/messenger/xmpp/stanzas/PresencePacket.java @@ -2,7 +2,7 @@ package de.pixart.messenger.xmpp.stanzas; public class PresencePacket extends AbstractAcknowledgeableStanza { - public PresencePacket() { - super("presence"); - } + public PresencePacket() { + super("presence"); + } } diff --git a/src/main/java/de/pixart/messenger/xmpp/stanzas/csi/ActivePacket.java b/src/main/java/de/pixart/messenger/xmpp/stanzas/csi/ActivePacket.java index 10abb3499..5d1cb61c3 100644 --- a/src/main/java/de/pixart/messenger/xmpp/stanzas/csi/ActivePacket.java +++ b/src/main/java/de/pixart/messenger/xmpp/stanzas/csi/ActivePacket.java @@ -3,8 +3,8 @@ package de.pixart.messenger.xmpp.stanzas.csi; import de.pixart.messenger.xmpp.stanzas.AbstractStanza; public class ActivePacket extends AbstractStanza { - public ActivePacket() { - super("active"); - setAttribute("xmlns", "urn:xmpp:csi:0"); - } + public ActivePacket() { + super("active"); + setAttribute("xmlns", "urn:xmpp:csi:0"); + } } diff --git a/src/main/java/de/pixart/messenger/xmpp/stanzas/csi/InactivePacket.java b/src/main/java/de/pixart/messenger/xmpp/stanzas/csi/InactivePacket.java index ebaad8205..9e840190d 100644 --- a/src/main/java/de/pixart/messenger/xmpp/stanzas/csi/InactivePacket.java +++ b/src/main/java/de/pixart/messenger/xmpp/stanzas/csi/InactivePacket.java @@ -3,8 +3,8 @@ package de.pixart.messenger.xmpp.stanzas.csi; import de.pixart.messenger.xmpp.stanzas.AbstractStanza; public class InactivePacket extends AbstractStanza { - public InactivePacket() { - super("inactive"); - setAttribute("xmlns", "urn:xmpp:csi:0"); - } + public InactivePacket() { + super("inactive"); + setAttribute("xmlns", "urn:xmpp:csi:0"); + } } diff --git a/src/main/java/de/pixart/messenger/xmpp/stanzas/streammgmt/AckPacket.java b/src/main/java/de/pixart/messenger/xmpp/stanzas/streammgmt/AckPacket.java index 5c8131878..f98707fbe 100644 --- a/src/main/java/de/pixart/messenger/xmpp/stanzas/streammgmt/AckPacket.java +++ b/src/main/java/de/pixart/messenger/xmpp/stanzas/streammgmt/AckPacket.java @@ -4,10 +4,10 @@ import de.pixart.messenger.xmpp.stanzas.AbstractStanza; public class AckPacket extends AbstractStanza { - public AckPacket(int sequence, int smVersion) { - super("a"); - this.setAttribute("xmlns", "urn:xmpp:sm:" + smVersion); - this.setAttribute("h", Integer.toString(sequence)); - } + public AckPacket(int sequence, int smVersion) { + super("a"); + this.setAttribute("xmlns", "urn:xmpp:sm:" + smVersion); + this.setAttribute("h", Integer.toString(sequence)); + } } diff --git a/src/main/java/de/pixart/messenger/xmpp/stanzas/streammgmt/EnablePacket.java b/src/main/java/de/pixart/messenger/xmpp/stanzas/streammgmt/EnablePacket.java index 2beca0f20..d4c8ab242 100644 --- a/src/main/java/de/pixart/messenger/xmpp/stanzas/streammgmt/EnablePacket.java +++ b/src/main/java/de/pixart/messenger/xmpp/stanzas/streammgmt/EnablePacket.java @@ -4,10 +4,10 @@ import de.pixart.messenger.xmpp.stanzas.AbstractStanza; public class EnablePacket extends AbstractStanza { - public EnablePacket(int smVersion) { - super("enable"); - this.setAttribute("xmlns", "urn:xmpp:sm:" + smVersion); - this.setAttribute("resume", "true"); - } + public EnablePacket(int smVersion) { + super("enable"); + this.setAttribute("xmlns", "urn:xmpp:sm:" + smVersion); + this.setAttribute("resume", "true"); + } } diff --git a/src/main/java/de/pixart/messenger/xmpp/stanzas/streammgmt/RequestPacket.java b/src/main/java/de/pixart/messenger/xmpp/stanzas/streammgmt/RequestPacket.java index 19880f3c6..cbd82eed6 100644 --- a/src/main/java/de/pixart/messenger/xmpp/stanzas/streammgmt/RequestPacket.java +++ b/src/main/java/de/pixart/messenger/xmpp/stanzas/streammgmt/RequestPacket.java @@ -4,9 +4,9 @@ import de.pixart.messenger.xmpp.stanzas.AbstractStanza; public class RequestPacket extends AbstractStanza { - public RequestPacket(int smVersion) { - super("r"); - this.setAttribute("xmlns", "urn:xmpp:sm:" + smVersion); - } + public RequestPacket(int smVersion) { + super("r"); + this.setAttribute("xmlns", "urn:xmpp:sm:" + smVersion); + } } diff --git a/src/main/java/de/pixart/messenger/xmpp/stanzas/streammgmt/ResumePacket.java b/src/main/java/de/pixart/messenger/xmpp/stanzas/streammgmt/ResumePacket.java index 1c7f8d08c..e47fc1b95 100644 --- a/src/main/java/de/pixart/messenger/xmpp/stanzas/streammgmt/ResumePacket.java +++ b/src/main/java/de/pixart/messenger/xmpp/stanzas/streammgmt/ResumePacket.java @@ -4,11 +4,11 @@ import de.pixart.messenger.xmpp.stanzas.AbstractStanza; public class ResumePacket extends AbstractStanza { - public ResumePacket(String id, int sequence, int smVersion) { - super("resume"); - this.setAttribute("xmlns", "urn:xmpp:sm:" + smVersion); - this.setAttribute("previd", id); - this.setAttribute("h", Integer.toString(sequence)); - } + public ResumePacket(String id, int sequence, int smVersion) { + super("resume"); + this.setAttribute("xmlns", "urn:xmpp:sm:" + smVersion); + this.setAttribute("previd", id); + this.setAttribute("h", Integer.toString(sequence)); + } } -- cgit v1.2.3