diff options
16 files changed, 198 insertions, 47 deletions
diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index 821de698..d716cfa4 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -54,6 +54,7 @@ public final class Config { public static final int PING_MAX_INTERVAL = 300; public static final int IDLE_PING_INTERVAL = 600; //540 is minimum according to docs; public static final int PING_MIN_INTERVAL = 30; + public static final int LOW_PING_TIMEOUT = 1; // used after push received public static final int PING_TIMEOUT = 15; public static final int SOCKET_TIMEOUT = 15; public static final int CONNECT_TIMEOUT = 90; diff --git a/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java b/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java index da315812..1a5367fd 100644 --- a/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java +++ b/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java @@ -259,8 +259,13 @@ public class PgpEngine { account); return; case OpenPgpApi.RESULT_CODE_ERROR: - logError(account, (OpenPgpError) result.getParcelableExtra(OpenPgpApi.RESULT_ERROR)); - callback.error(R.string.unable_to_connect_to_keychain, account); + 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); + } } } }); diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 1bc5fa83..e776a78b 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -185,8 +185,8 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } private void fillMap(SQLiteAxolotlStore store) { - List<Integer> deviceIds = store.getSubDeviceSessions(account.getJid().toBareJid().toString()); - putDevicesForJid(account.getJid().toBareJid().toString(), deviceIds, store); + List<Integer> 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(); @@ -220,7 +220,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { public void clearErrorFor(Jid jid) { synchronized (MAP_LOCK) { - Map<Integer, FetchStatus> devices = this.map.get(jid.toBareJid().toString()); + Map<Integer, FetchStatus> devices = this.map.get(jid.toBareJid().toPreppedString()); if (devices == null) { return; } @@ -257,28 +257,28 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust) { - return axolotlStore.getContactKeysWithTrust(account.getJid().toBareJid().toString(), trust); + return axolotlStore.getContactKeysWithTrust(account.getJid().toBareJid().toPreppedString(), trust); } public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, Jid jid) { - return axolotlStore.getContactKeysWithTrust(jid.toBareJid().toString(), trust); + return axolotlStore.getContactKeysWithTrust(jid.toBareJid().toPreppedString(), trust); } public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, List<Jid> jids) { Set<IdentityKey> keys = new HashSet<>(); for(Jid jid : jids) { - keys.addAll(axolotlStore.getContactKeysWithTrust(jid.toString(), trust)); + keys.addAll(axolotlStore.getContactKeysWithTrust(jid.toPreppedString(), trust)); } return keys; } public long getNumTrustedKeys(Jid jid) { - return axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toString()); + return axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toPreppedString()); } public boolean anyTargetHasNoTrustedKeys(List<Jid> jids) { for(Jid jid : jids) { - if (axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toString()) == 0) { + if (axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toPreppedString()) == 0) { return true; } } @@ -286,7 +286,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } private AxolotlAddress getAddressForJid(Jid jid) { - return new AxolotlAddress(jid.toString(), 0); + return new AxolotlAddress(jid.toPreppedString(), 0); } private Set<XmppAxolotlSession> findOwnSessions() { @@ -359,7 +359,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { final XmppAxolotlSession.Trust from, final XmppAxolotlSession.Trust to) { for (Integer deviceId : deviceIds) { - AxolotlAddress address = new AxolotlAddress(jid.toBareJid().toString(), deviceId); + AxolotlAddress address = new AxolotlAddress(jid.toBareJid().toPreppedString(), deviceId); XmppAxolotlSession session = sessions.get(address); if (session != null && session.getFingerprint() != null && session.getTrust() == from) { @@ -381,13 +381,13 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { publishOwnDeviceId(deviceIds); } for (Integer deviceId : deviceIds) { - AxolotlAddress ownDeviceAddress = new AxolotlAddress(jid.toBareJid().toString(), deviceId); + AxolotlAddress ownDeviceAddress = new AxolotlAddress(jid.toBareJid().toPreppedString(), deviceId); if (sessions.get(ownDeviceAddress) == null) { buildSessionFromPEP(ownDeviceAddress); } } } - Set<Integer> expiredDevices = new HashSet<>(axolotlStore.getSubDeviceSessions(jid.toBareJid().toString())); + Set<Integer> expiredDevices = new HashSet<>(axolotlStore.getSubDeviceSessions(jid.toBareJid().toPreppedString())); expiredDevices.removeAll(deviceIds); setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.TRUSTED, XmppAxolotlSession.Trust.INACTIVE_TRUSTED); @@ -759,7 +759,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } private void finishBuildingSessionsFromPEP(final AxolotlAddress address) { - AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0); + AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), 0); if (!fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING) && !fetchStatusMap.getAll(address).containsValue(FetchStatus.PENDING)) { FetchStatus report = null; @@ -873,7 +873,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } if (deviceIds.get(account.getJid().toBareJid()) != null) { for (Integer ownId : this.deviceIds.get(account.getJid().toBareJid())) { - AxolotlAddress address = new AxolotlAddress(account.getJid().toBareJid().toString(), ownId); + AxolotlAddress address = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), ownId); if (sessions.get(address) == null) { IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey(); if (identityKey != null) { @@ -933,12 +933,12 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } public boolean hasPendingKeyFetches(Account account, List<Jid> jids) { - AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0); + 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().toString(), 0); + AxolotlAddress foreignAddress = new AxolotlAddress(jid.toBareJid().toPreppedString(), 0); if (fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING)) { return true; } diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java index 70af45d4..55f5443a 100644 --- a/src/main/java/eu/siacs/conversations/entities/Contact.java +++ b/src/main/java/eu/siacs/conversations/entities/Contact.java @@ -196,7 +196,7 @@ public class Contact implements ListItem, Blockable { values.put(ACCOUNT, accountUuid); values.put(SYSTEMNAME, systemName); values.put(SERVERNAME, serverName); - values.put(JID, jid.toString()); + values.put(JID, jid.toPreppedString()); values.put(OPTIONS, subscription); values.put(SYSTEMACCOUNT, systemAccount); values.put(PHOTOURI, photoUri); @@ -209,10 +209,6 @@ public class Contact implements ListItem, Blockable { } } - public int getSubscription() { - return this.subscription; - } - public Account getAccount() { return this.account; } diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index d9a03fc9..d94e1fec 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -506,7 +506,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl values.put(NAME, name); values.put(CONTACT, contactUuid); values.put(ACCOUNT, accountUuid); - values.put(CONTACTJID, contactJid.toString()); + values.put(CONTACTJID, contactJid.toPreppedString()); values.put(CREATED, created); values.put(STATUS, status); values.put(MODE, mode); diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index b00d8ef3..821eea39 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -204,12 +204,12 @@ public class Message extends AbstractEntity { if (counterpart == null) { values.putNull(COUNTERPART); } else { - values.put(COUNTERPART, counterpart.toString()); + values.put(COUNTERPART, counterpart.toPreppedString()); } if (trueCounterpart == null) { values.putNull(TRUE_COUNTERPART); } else { - values.put(TRUE_COUNTERPART, trueCounterpart.toString()); + values.put(TRUE_COUNTERPART, trueCounterpart.toPreppedString()); } values.put(BODY, body); values.put(TIME_SENT, timeSent); diff --git a/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java b/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java index f9fed914..f8639885 100644 --- a/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java @@ -38,13 +38,17 @@ public class PresenceGenerator extends AbstractGenerator { } 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(); - if (sig != null && mXmppConnectionService.getPgpEngine() != null) { + if (includePgpAnnouncement && sig != null && mXmppConnectionService.getPgpEngine() != null) { packet.addChild("x", "jabber:x:signed").setContent(sig); } String capHash = getCapHash(); diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index d8b6b4e1..c1063762 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -287,7 +287,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { continue; } int ownDeviceId = Integer.valueOf(ownDeviceIdString); - AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), ownDeviceId); + AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), ownDeviceId); deleteSession(db, account, ownAddress); IdentityKeyPair identityKeyPair = loadOwnIdentityKeyPair(db, account); if (identityKeyPair != null) { @@ -345,7 +345,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { try { newJid = Jid.fromString( cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID)) - ).toString(); + ).toPreppedString(); } catch (InvalidJidException ignored) { Log.e(Config.LOGTAG, "Failed to migrate Conversation CONTACTJID " + cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID)) @@ -370,7 +370,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { try { newJid = Jid.fromString( cursor.getString(cursor.getColumnIndex(Contact.JID)) - ).toString(); + ).toPreppedString(); } catch (InvalidJidException ignored) { Log.e(Config.LOGTAG, "Failed to migrate Contact JID " + cursor.getString(cursor.getColumnIndex(Contact.JID)) @@ -578,8 +578,8 @@ public class DatabaseBackend extends SQLiteOpenHelper { public Conversation findConversation(final Account account, final Jid contactJid) { SQLiteDatabase db = this.getReadableDatabase(); String[] selectionArgs = {account.getUuid(), - contactJid.toBareJid().toString() + "/%", - contactJid.toBareJid().toString() + contactJid.toBareJid().toPreppedString() + "/%", + contactJid.toBareJid().toPreppedString() }; Cursor cursor = db.query(Conversation.TABLENAME, null, Conversation.ACCOUNT + "=? AND (" + Conversation.CONTACTJID @@ -691,7 +691,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.insert(Contact.TABLENAME, null, contact.getContentValues()); } else { String where = Contact.ACCOUNT + "=? AND " + Contact.JID + "=?"; - String[] whereArgs = {account.getUuid(), contact.getJid().toString()}; + String[] whereArgs = {account.getUuid(), contact.getJid().toPreppedString()}; db.delete(Contact.TABLENAME, where, whereArgs); } } @@ -1025,7 +1025,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { } private IdentityKeyPair loadOwnIdentityKeyPair(SQLiteDatabase db, Account account) { - String name = account.getJid().toBareJid().toString(); + String name = account.getJid().toBareJid().toPreppedString(); IdentityKeyPair identityKeyPair = null; Cursor cursor = getIdentityKeyCursor(db, account, name, true); if (cursor.getCount() != 0) { @@ -1181,7 +1181,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { } public void storeOwnIdentityKeyPair(Account account, IdentityKeyPair identityKeyPair) { - storeIdentityKey(account, account.getJid().toBareJid().toString(), true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), XmppAxolotlSession.Trust.TRUSTED); + storeIdentityKey(account, account.getJid().toBareJid().toPreppedString(), true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), XmppAxolotlSession.Trust.TRUSTED); } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index e96704f3..08921516 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -145,6 +145,7 @@ public class XmppConnectionService extends Service { private final List<Conversation> conversations = new CopyOnWriteArrayList<>(); private final IqGenerator mIqGenerator = new IqGenerator(this); private final List<String> mInProgressAvatarFetches = new ArrayList<>(); + private final HashSet<Jid> mLowPingTimeoutMode = new HashSet<>(); private long mLastActivity = 0; @@ -626,7 +627,8 @@ public class XmppConnectionService extends Service { long lastSent = account.getXmppConnection().getLastPingSent(); long pingInterval = (Config.PUSH_MODE || "ui".equals(action)) ? Config.PING_MIN_INTERVAL * 1000 : Config.PING_MAX_INTERVAL * 1000; long msToNextPing = (Math.max(lastReceived, lastSent) + pingInterval) - SystemClock.elapsedRealtime(); - long pingTimeoutIn = (lastSent + Config.PING_TIMEOUT * 1000) - 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"); @@ -634,10 +636,18 @@ public class XmppConnectionService extends Service { } else { int secs = (int) (pingTimeoutIn / 1000); this.scheduleWakeUpCall(secs, account.getUuid().hashCode()); + if (mLowPingTimeoutMode.remove(account.getJid().toBareJid())) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": leaving low ping timeout mode"); + } } } else { pingCandidates.add(account); - if (msToNextPing <= 0 || CryptoHelper.getAccountFingerprint(account).equals(pushedAccountHash)) { + 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()); @@ -2037,11 +2047,11 @@ public class XmppConnectionService extends Service { 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); + 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(conversation.getMucOptions().getPassword()); + x.addChild("password").setContent(mucOptions.getPassword()); } if (mucOptions.mamSupport()) { diff --git a/src/main/java/eu/siacs/conversations/ui/EditMessage.java b/src/main/java/eu/siacs/conversations/ui/EditMessage.java index e3841d1d..e609d08e 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditMessage.java +++ b/src/main/java/eu/siacs/conversations/ui/EditMessage.java @@ -1,7 +1,11 @@ package eu.siacs.conversations.ui; import android.content.Context; +import android.os.Build; import android.os.Handler; +import android.text.Editable; +import android.text.InputFilter; +import android.text.Spanned; import android.util.AttributeSet; import android.view.KeyEvent; import android.widget.EditText; @@ -89,4 +93,36 @@ public class EditMessage extends EditText { boolean onTabPressed(boolean repeated); } + private static final InputFilter SPAN_FILTER = new InputFilter() { + + @Override + public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { + return source instanceof Spanned ? source.toString() : source; + } + }; + + @Override + public boolean onTextContextMenuItem(int id) { + if (id == android.R.id.paste) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return super.onTextContextMenuItem(android.R.id.pasteAsPlainText); + } else { + Editable editable = getEditableText(); + InputFilter[] filters = editable.getFilters(); + InputFilter[] tempFilters = new InputFilter[filters != null ? filters.length + 1 : 1]; + if (filters != null) { + System.arraycopy(filters, 0, tempFilters, 1, filters.length); + } + tempFilters[0] = SPAN_FILTER; + editable.setFilters(tempFilters); + try { + return super.onTextContextMenuItem(id); + } finally { + editable.setFilters(filters); + } + } + } else { + return super.onTextContextMenuItem(id); + } + } } diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 5172e18d..c2dfa1d8 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -584,7 +584,14 @@ public abstract class XmppActivity extends Activity { @Override public void error(int error, Account account) { - displayErrorDialog(error); + if (error == 0 && account != null) { + account.setPgpSignId(0); + account.unsetPgpSignature(); + xmppConnectionService.databaseBackend.updateAccount(account); + choosePgpSignId(account); + } else { + displayErrorDialog(error); + } } }); } diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index 336d9eab..c04cb1a5 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -54,12 +54,13 @@ import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.ui.ConversationActivity; import eu.siacs.conversations.ui.widget.ClickableMovementMethod; +import eu.siacs.conversations.ui.widget.CopyTextView; import eu.siacs.conversations.ui.widget.ListSelectionManager; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.GeoHelper; import eu.siacs.conversations.utils.UIHelper; -public class MessageAdapter extends ArrayAdapter<Message> { +public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextView.CopyHandler { private static final int SENT = 0; private static final int RECEIVED = 1; @@ -485,7 +486,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { viewHolder.edit_indicator = (ImageView) view.findViewById(R.id.edit_indicator); viewHolder.image = (ImageView) view .findViewById(R.id.message_image); - viewHolder.messageBody = (TextView) view + viewHolder.messageBody = (CopyTextView) view .findViewById(R.id.message_body); viewHolder.time = (TextView) view .findViewById(R.id.message_time); @@ -506,7 +507,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { viewHolder.edit_indicator = (ImageView) view.findViewById(R.id.edit_indicator); viewHolder.image = (ImageView) view .findViewById(R.id.message_image); - viewHolder.messageBody = (TextView) view + viewHolder.messageBody = (CopyTextView) view .findViewById(R.id.message_body); viewHolder.time = (TextView) view .findViewById(R.id.message_time); @@ -524,7 +525,10 @@ public class MessageAdapter extends ArrayAdapter<Message> { viewHolder = null; break; } - if (viewHolder.messageBody != null) listSelectionManager.onCreate(viewHolder.messageBody); + if (viewHolder.messageBody != null) { + listSelectionManager.onCreate(viewHolder.messageBody); + viewHolder.messageBody.setCopyHandler(this); + } view.setTag(viewHolder); } else { viewHolder = (ViewHolder) view.getTag(); @@ -682,6 +686,11 @@ public class MessageAdapter extends ArrayAdapter<Message> { 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()) { @@ -759,7 +768,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { protected ImageView indicator; protected ImageView indicatorReceived; protected TextView time; - protected TextView messageBody; + protected CopyTextView messageBody; protected ImageView contact_picture; protected TextView status_message; protected TextView encryption; diff --git a/src/main/java/eu/siacs/conversations/ui/widget/CopyTextView.java b/src/main/java/eu/siacs/conversations/ui/widget/CopyTextView.java new file mode 100644 index 00000000..bed56192 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/widget/CopyTextView.java @@ -0,0 +1,66 @@ +package eu.siacs.conversations.ui.widget; + +import android.annotation.TargetApi; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.os.Build; +import android.util.AttributeSet; +import android.widget.TextView; + +public class CopyTextView extends TextView { + + public CopyTextView(Context context) { + super(context); + } + + public CopyTextView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public CopyTextView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @SuppressWarnings("unused") + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public CopyTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public interface CopyHandler { + public String transformTextForCopy(CharSequence text, int start, int end); + } + + private CopyHandler copyHandler; + + public void setCopyHandler(CopyHandler copyHandler) { + this.copyHandler = copyHandler; + } + + @Override + public boolean onTextContextMenuItem(int id) { + CharSequence text = getText(); + int min = 0; + int max = text.length(); + if (isFocused()) { + final int selStart = getSelectionStart(); + final int selEnd = getSelectionEnd(); + min = Math.max(0, Math.min(selStart, selEnd)); + max = Math.max(0, Math.max(selStart, selEnd)); + } + String textForCopy = null; + if (id == android.R.id.copy && copyHandler != null) { + textForCopy = copyHandler.transformTextForCopy(getText(), min, max); + } + try { + return super.onTextContextMenuItem(id); + } finally { + if (textForCopy != null) { + ClipboardManager clipboard = (ClipboardManager) getContext(). + getSystemService(Context.CLIPBOARD_SERVICE); + clipboard.setPrimaryClip(ClipData.newPlainText(null, textForCopy)); + } + } + } +}
\ No newline at end of file diff --git a/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java b/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java index 6430d41e..20f1feb4 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java @@ -21,6 +21,10 @@ public final class Jid { private final String domainpart; private final String resourcepart; + // It's much more efficient to store the ful 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; } @@ -69,6 +73,7 @@ public final class Jid { Jid fromCache = Jid.cache.get(jid); if (fromCache != null) { + displayjid = fromCache.displayjid; localpart = fromCache.localpart; domainpart = fromCache.domainpart; resourcepart = fromCache.resourcepart; @@ -89,6 +94,8 @@ public final class Jid { throw new InvalidJidException(InvalidJidException.INVALID_CHARACTER); } + String finaljid; + final int domainpartStart; final int atLoc = jid.indexOf("@"); final int slashLoc = jid.indexOf("/"); @@ -96,6 +103,7 @@ public final class Jid { // 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); @@ -108,6 +116,7 @@ public final class Jid { throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH); } domainpartStart = atLoc + 1; + finaljid = lp + "@"; } final String dp; @@ -126,6 +135,7 @@ public final class Jid { } catch (final StringprepException e) { throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e); } + finaljid = finaljid + dp + "/" + rp; } else { resourcepart = ""; try{ @@ -133,6 +143,7 @@ public final class Jid { } catch (final StringprepException e) { throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e); } + finaljid = finaljid + dp; } // Remove trailing "." before storing the domain part. @@ -156,6 +167,8 @@ public final class Jid { } Jid.cache.put(jid, this); + + this.displayjid = finaljid; } public Jid toBareJid() { @@ -178,6 +191,10 @@ public final class Jid { @Override public String toString() { + return displayjid; + } + + public String toPreppedString() { String out; if (hasLocalpart()) { out = localpart + '@' + domainpart; diff --git a/src/main/res/layout/message_received.xml b/src/main/res/layout/message_received.xml index 18f753af..ea098b2d 100644 --- a/src/main/res/layout/message_received.xml +++ b/src/main/res/layout/message_received.xml @@ -49,7 +49,7 @@ android:background="@color/black87" android:scaleType="centerCrop" /> - <TextView + <eu.siacs.conversations.ui.widget.CopyTextView android:id="@+id/message_body" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/src/main/res/layout/message_sent.xml b/src/main/res/layout/message_sent.xml index 5def72dc..0f81c3f7 100644 --- a/src/main/res/layout/message_sent.xml +++ b/src/main/res/layout/message_sent.xml @@ -50,7 +50,7 @@ android:longClickable="true" android:scaleType="centerCrop" /> - <TextView + <eu.siacs.conversations.ui.widget.CopyTextView android:id="@+id/message_body" android:layout_width="wrap_content" android:layout_height="wrap_content" |