opt-in to send last userinteraction in presence

This commit is contained in:
Daniel Gultsch 2016-06-04 16:16:14 +02:00
parent 6639d0f23b
commit 71e9117176
16 changed files with 165 additions and 71 deletions

View file

@ -19,6 +19,7 @@
* XEP-0280: Message Carbons * XEP-0280: Message Carbons
* XEP-0308: Last Message Correction * XEP-0308: Last Message Correction
* XEP-0313: Message Archive Management * XEP-0313: Message Archive Management
* XEP-0319: Last User Interaction in Presence
* XEP-0333: Chat Markers * XEP-0333: Chat Markers
* XEP-0352: Client State Indication * XEP-0352: Client State Indication
* XEP-0357: Push Notifications * XEP-0357: Push Notifications

View file

@ -34,7 +34,6 @@ public class Contact implements ListItem, Blockable {
public static final String LAST_PRESENCE = "last_presence"; public static final String LAST_PRESENCE = "last_presence";
public static final String LAST_TIME = "last_time"; public static final String LAST_TIME = "last_time";
public static final String GROUPS = "groups"; public static final String GROUPS = "groups";
public Lastseen lastseen = new Lastseen();
protected String accountUuid; protected String accountUuid;
protected String systemName; protected String systemName;
protected String serverName; protected String serverName;
@ -50,9 +49,14 @@ public class Contact implements ListItem, Blockable {
protected Account account; protected Account account;
protected Avatar avatar; 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, public Contact(final String account, final String systemName, final String serverName,
final Jid jid, final int subscription, final String photoUri, final Jid jid, final int subscription, final String photoUri,
final String systemAccount, final String keys, final String avatar, final Lastseen lastseen, final String groups) { final String systemAccount, final String keys, final String avatar, final long lastseen,
final String presence, final String groups) {
this.accountUuid = account; this.accountUuid = account;
this.systemName = systemName; this.systemName = systemName;
this.serverName = serverName; this.serverName = serverName;
@ -75,7 +79,8 @@ public class Contact implements ListItem, Blockable {
} catch (JSONException e) { } catch (JSONException e) {
this.groups = new JSONArray(); this.groups = new JSONArray();
} }
this.lastseen = lastseen; this.mLastseen = lastseen;
this.mLastPresence = presence;
} }
public Contact(final Jid jid) { public Contact(final Jid jid) {
@ -83,9 +88,6 @@ public class Contact implements ListItem, Blockable {
} }
public static Contact fromCursor(final Cursor cursor) { public static Contact fromCursor(final Cursor cursor) {
final Lastseen lastseen = new Lastseen(
cursor.getString(cursor.getColumnIndex(LAST_PRESENCE)),
cursor.getLong(cursor.getColumnIndex(LAST_TIME)));
final Jid jid; final Jid jid;
try { try {
jid = Jid.fromString(cursor.getString(cursor.getColumnIndex(JID)), true); jid = Jid.fromString(cursor.getString(cursor.getColumnIndex(JID)), true);
@ -102,7 +104,8 @@ public class Contact implements ListItem, Blockable {
cursor.getString(cursor.getColumnIndex(SYSTEMACCOUNT)), cursor.getString(cursor.getColumnIndex(SYSTEMACCOUNT)),
cursor.getString(cursor.getColumnIndex(KEYS)), cursor.getString(cursor.getColumnIndex(KEYS)),
cursor.getString(cursor.getColumnIndex(AVATAR)), cursor.getString(cursor.getColumnIndex(AVATAR)),
lastseen, cursor.getLong(cursor.getColumnIndex(LAST_TIME)),
cursor.getString(cursor.getColumnIndex(LAST_PRESENCE)),
cursor.getString(cursor.getColumnIndex(GROUPS))); cursor.getString(cursor.getColumnIndex(GROUPS)));
} }
@ -197,8 +200,8 @@ public class Contact implements ListItem, Blockable {
values.put(PHOTOURI, photoUri); values.put(PHOTOURI, photoUri);
values.put(KEYS, keys.toString()); values.put(KEYS, keys.toString());
values.put(AVATAR, avatar == null ? null : avatar.getFilename()); values.put(AVATAR, avatar == null ? null : avatar.getFilename());
values.put(LAST_PRESENCE, lastseen.presence); values.put(LAST_PRESENCE, mLastPresence);
values.put(LAST_TIME, lastseen.time); values.put(LAST_TIME, mLastseen);
values.put(GROUPS, groups.toString()); values.put(GROUPS, groups.toString());
return values; return values;
} }
@ -517,18 +520,32 @@ public class Contact implements ListItem, Blockable {
this.commonName = cn; this.commonName = cn;
} }
public static class Lastseen { public void flagActive() {
public long time; this.mActive = true;
public String presence;
public Lastseen() {
this(null, 0);
} }
public Lastseen(final String presence, final long time) { public void flagInactive() {
this.presence = presence; this.mActive = false;
this.time = time;
} }
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 setLastPresence(String presence) {
this.mLastPresence = presence;
}
public String getLastPresence() {
return this.mLastPresence;
} }
public final class Options { public final class Options {

View file

@ -16,8 +16,6 @@ import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.PhoneHelper; import eu.siacs.conversations.utils.PhoneHelper;
import eu.siacs.conversations.xmpp.jid.Jid;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
public abstract class AbstractGenerator { public abstract class AbstractGenerator {
private final String[] FEATURES = { private final String[] FEATURES = {
@ -33,7 +31,8 @@ public abstract class AbstractGenerator {
"http://jabber.org/protocol/nick+notify", "http://jabber.org/protocol/nick+notify",
"urn:xmpp:ping", "urn:xmpp:ping",
"jabber:iq:version", "jabber:iq:version",
"http://jabber.org/protocol/chatstates"}; "http://jabber.org/protocol/chatstates"
};
private final String[] MESSAGE_CONFIRMATION_FEATURES = { private final String[] MESSAGE_CONFIRMATION_FEATURES = {
"urn:xmpp:chat-markers:0", "urn:xmpp:chat-markers:0",
"urn:xmpp:receipts" "urn:xmpp:receipts"
@ -45,7 +44,7 @@ public abstract class AbstractGenerator {
protected final String IDENTITY_NAME = "Conversations"; protected final String IDENTITY_NAME = "Conversations";
protected final String IDENTITY_TYPE = "phone"; protected final String IDENTITY_TYPE = "phone";
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'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;

View file

@ -1,9 +1,7 @@
package eu.siacs.conversations.parser; package eu.siacs.conversations.parser;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale; import java.util.Locale;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
@ -14,7 +12,6 @@ import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.jid.Jid;
import eu.siacs.conversations.xmpp.stanzas.AbstractStanza;
public abstract class AbstractParser { public abstract class AbstractParser {
@ -24,42 +21,48 @@ public abstract class AbstractParser {
this.mXmppConnectionService = service; this.mXmppConnectionService = service;
} }
public static Long getTimestamp(Element element, Long defaultValue) { public static Long parseTimestamp(Element element, Long d) {
Element delay = element.findChild("delay","urn:xmpp:delay"); Element delay = element.findChild("delay","urn:xmpp:delay");
if (delay != null) { if (delay != null) {
String stamp = delay.getAttribute("stamp"); String stamp = delay.getAttribute("stamp");
if (stamp != null) { if (stamp != null) {
try { try {
return AbstractParser.parseTimestamp(delay.getAttribute("stamp")).getTime(); return AbstractParser.parseTimestamp(delay.getAttribute("stamp"));
} catch (ParseException e) { } catch (ParseException e) {
return defaultValue; return d;
} }
} }
} }
return defaultValue; return d;
} }
protected long getTimestamp(Element packet) { public static long parseTimestamp(Element element) {
return getTimestamp(packet,System.currentTimeMillis()); return parseTimestamp(element, System.currentTimeMillis());
} }
public static Date parseTimestamp(String timestamp) throws ParseException { public static long parseTimestamp(String timestamp) throws ParseException {
timestamp = timestamp.replace("Z", "+0000"); timestamp = timestamp.replace("Z", "+0000");
SimpleDateFormat dateFormat; 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()); timestamp = timestamp.substring(0,19)+timestamp.substring(timestamp.length() -5,timestamp.length());
dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ",Locale.US); dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ",Locale.US);
return dateFormat.parse(timestamp); return Math.min(dateFormat.parse(timestamp).getTime()+ms, System.currentTimeMillis());
} }
protected void updateLastseen(long timestamp, final Account account, final Jid from) { protected void updateLastseen(final Account account, final Jid from) {
final String presence = from == null || from.isBareJid() ? "" : from.getResourcepart();
final Contact contact = account.getRoster().getContact(from); final Contact contact = account.getRoster().getContact(from);
if (timestamp >= contact.lastseen.time) { contact.setLastPresence(from.isBareJid() ? "" : from.getResourcepart());
contact.lastseen.time = timestamp;
if (!presence.isEmpty()) {
contact.lastseen.presence = presence;
}
}
} }
protected String avatarData(Element items) { protected String avatarData(Element items) {

View file

@ -7,12 +7,12 @@ import android.util.Pair;
import net.java.otr4j.session.Session; import net.java.otr4j.session.Session;
import net.java.otr4j.session.SessionStatus; import net.java.otr4j.session.SessionStatus;
import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -32,6 +32,7 @@ import eu.siacs.conversations.http.HttpConnectionManager;
import eu.siacs.conversations.services.MessageArchiveService; import eu.siacs.conversations.services.MessageArchiveService;
import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.Xmlns;
import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.OnMessagePacketReceived; import eu.siacs.conversations.xmpp.OnMessagePacketReceived;
import eu.siacs.conversations.xmpp.chatstate.ChatState; import eu.siacs.conversations.xmpp.chatstate.ChatState;
@ -328,7 +329,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
} }
if (timestamp == null) { if (timestamp == null) {
timestamp = AbstractParser.getTimestamp(packet, System.currentTimeMillis()); timestamp = AbstractParser.parseTimestamp(packet);
} }
final String body = packet.getBody(); final String body = packet.getBody();
final Element mucUserElement = packet.findChild("x", "http://jabber.org/protocol/muc#user"); final Element mucUserElement = packet.findChild("x", "http://jabber.org/protocol/muc#user");
@ -439,7 +440,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
message.setType(Message.TYPE_PRIVATE); message.setType(Message.TYPE_PRIVATE);
} }
} else { } else {
updateLastseen(timestamp, account, from); updateLastseen(account, from);
} }
if (replacementId != null && mXmppConnectionService.allowMessageCorrection()) { if (replacementId != null && mXmppConnectionService.allowMessageCorrection()) {
@ -601,7 +602,6 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
mXmppConnectionService.markRead(conversation); mXmppConnectionService.markRead(conversation);
} }
} else { } else {
updateLastseen(timestamp, account, from);
final Message displayedMessage = mXmppConnectionService.markMessage(account, from.toBareJid(), displayed.getAttribute("id"), Message.STATUS_SEND_DISPLAYED); final Message displayedMessage = mXmppConnectionService.markMessage(account, from.toBareJid(), displayed.getAttribute("id"), Message.STATUS_SEND_DISPLAYED);
Message message = displayedMessage == null ? null : displayedMessage.prev(); Message message = displayedMessage == null ? null : displayedMessage.prev();
while (message != null while (message != null

View file

@ -2,6 +2,7 @@ package eu.siacs.conversations.parser;
import android.util.Log; import android.util.Log;
import java.text.ParseException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -206,6 +207,20 @@ public class PresenceParser extends AbstractParser implements
mXmppConnectionService.fetchCaps(account, from, presence); 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));
}
PgpEngine pgp = mXmppConnectionService.getPgpEngine(); PgpEngine pgp = mXmppConnectionService.getPgpEngine();
Element x = packet.findChild("x", "jabber:x:signed"); Element x = packet.findChild("x", "jabber:x:signed");
if (pgp != null && x != null) { if (pgp != null && x != null) {

View file

@ -20,7 +20,6 @@ import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.FileObserver; import android.os.FileObserver;
import android.os.IBinder; import android.os.IBinder;
import android.os.Looper;
import android.os.PowerManager; import android.os.PowerManager;
import android.os.PowerManager.WakeLock; import android.os.PowerManager.WakeLock;
import android.os.SystemClock; import android.os.SystemClock;
@ -80,6 +79,7 @@ import eu.siacs.conversations.entities.Roster;
import eu.siacs.conversations.entities.ServiceDiscoveryResult; import eu.siacs.conversations.entities.ServiceDiscoveryResult;
import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.Transferable;
import eu.siacs.conversations.entities.TransferablePlaceholder; import eu.siacs.conversations.entities.TransferablePlaceholder;
import eu.siacs.conversations.generator.AbstractGenerator;
import eu.siacs.conversations.generator.IqGenerator; import eu.siacs.conversations.generator.IqGenerator;
import eu.siacs.conversations.generator.MessageGenerator; import eu.siacs.conversations.generator.MessageGenerator;
import eu.siacs.conversations.generator.PresenceGenerator; import eu.siacs.conversations.generator.PresenceGenerator;
@ -141,6 +141,9 @@ public class XmppConnectionService extends Service {
private final List<Conversation> conversations = new CopyOnWriteArrayList<>(); private final List<Conversation> conversations = new CopyOnWriteArrayList<>();
private final IqGenerator mIqGenerator = new IqGenerator(this); private final IqGenerator mIqGenerator = new IqGenerator(this);
private final List<String> mInProgressAvatarFetches = new ArrayList<>(); private final List<String> mInProgressAvatarFetches = new ArrayList<>();
private long mLastActivity = 0;
public DatabaseBackend databaseBackend; public DatabaseBackend databaseBackend;
private ContentObserver contactObserver = new ContentObserver(null) { private ContentObserver contactObserver = new ContentObserver(null) {
@Override @Override
@ -1584,6 +1587,7 @@ public class XmppConnectionService extends Service {
public void setOnConversationListChangedListener(OnConversationUpdate listener) { public void setOnConversationListChangedListener(OnConversationUpdate listener) {
synchronized (this) { synchronized (this) {
this.mLastActivity = System.currentTimeMillis();
if (checkListeners()) { if (checkListeners()) {
switchToForeground(); switchToForeground();
} }
@ -1796,22 +1800,29 @@ public class XmppConnectionService extends Service {
} }
private void switchToForeground() { private void switchToForeground() {
final boolean broadcastLastActivity = broadcastLastActivity();
for (Conversation conversation : getConversations()) { for (Conversation conversation : getConversations()) {
conversation.setIncomingChatState(ChatState.ACTIVE); conversation.setIncomingChatState(ChatState.ACTIVE);
} }
for (Account account : getAccounts()) { for (Account account : getAccounts()) {
if (account.getStatus() == Account.State.ONLINE) { if (account.getStatus() == Account.State.ONLINE) {
account.deactivateGracePeriod(); account.deactivateGracePeriod();
XmppConnection connection = account.getXmppConnection(); final XmppConnection connection = account.getXmppConnection();
if (connection != null && connection.getFeatures().csi()) { if (connection != null ) {
if (connection.getFeatures().csi()) {
connection.sendActive(); 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"); Log.d(Config.LOGTAG, "app switched into foreground");
} }
private void switchToBackground() { private void switchToBackground() {
final boolean broadcastLastActivity = broadcastLastActivity();
for (Account account : getAccounts()) { for (Account account : getAccounts()) {
if (account.getStatus() == Account.State.ONLINE) { if (account.getStatus() == Account.State.ONLINE) {
XmppConnection connection = account.getXmppConnection(); XmppConnection connection = account.getXmppConnection();
@ -1819,6 +1830,9 @@ public class XmppConnectionService extends Service {
if (connection.getFeatures().csi()) { if (connection.getFeatures().csi()) {
connection.sendInactive(); connection.sendInactive();
} }
if (broadcastLastActivity) {
sendPresence(account, broadcastLastActivity);
}
if (Config.CLOSE_TCP_WHEN_SWITCHING_TO_BACKGROUND && mPushManagementService.available(account)) { if (Config.CLOSE_TCP_WHEN_SWITCHING_TO_BACKGROUND && mPushManagementService.available(account)) {
connection.waitForPush(); connection.waitForPush();
cancelWakeUpCall(account.getUuid().hashCode()); cancelWakeUpCall(account.getUuid().hashCode());
@ -2253,6 +2267,7 @@ public class XmppConnectionService extends Service {
private void disconnect(Account account, boolean force) { private void disconnect(Account account, boolean force) {
if ((account.getStatus() == Account.State.ONLINE) if ((account.getStatus() == Account.State.ONLINE)
|| (account.getStatus() == Account.State.DISABLED)) { || (account.getStatus() == Account.State.DISABLED)) {
final XmppConnection connection = account.getXmppConnection();
if (!force) { if (!force) {
List<Conversation> conversations = getConversations(); List<Conversation> conversations = getConversations();
for (Conversation conversation : conversations) { for (Conversation conversation : conversations) {
@ -2270,7 +2285,7 @@ public class XmppConnectionService extends Service {
} }
sendOfflinePresence(account); sendOfflinePresence(account);
} }
account.getXmppConnection().disconnect(force); connection.disconnect(force);
} }
} }
@ -2814,6 +2829,10 @@ public class XmppConnectionService extends Service {
return getPreferences().getBoolean("show_connection_options", false); return getPreferences().getBoolean("show_connection_options", false);
} }
public boolean broadcastLastActivity() {
return getPreferences().getBoolean("last_activity", false);
}
public int unreadCount() { public int unreadCount() {
int count = 0; int count = 0;
for (Conversation conversation : getConversations()) { for (Conversation conversation : getConversations()) {
@ -3052,6 +3071,10 @@ public class XmppConnectionService extends Service {
} }
public void sendPresence(final Account account) { public void sendPresence(final Account account) {
sendPresence(account, checkListeners() && broadcastLastActivity());
}
private void sendPresence(final Account account, final boolean includeIdleTimestamp) {
PresencePacket packet; PresencePacket packet;
if (manuallyChangePresence()) { if (manuallyChangePresence()) {
packet = mPresenceGenerator.selfPresence(account, account.getPresenceStatus()); packet = mPresenceGenerator.selfPresence(account, account.getPresenceStatus());
@ -3062,6 +3085,10 @@ public class XmppConnectionService extends Service {
} else { } else {
packet = mPresenceGenerator.selfPresence(account, getTargetPresence()); 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); sendPresencePacket(account, packet);
} }
@ -3072,9 +3099,10 @@ public class XmppConnectionService extends Service {
} }
public void refreshAllPresences() { public void refreshAllPresences() {
boolean includeIdleTimestamp = checkListeners() && broadcastLastActivity();
for (Account account : getAccounts()) { for (Account account : getAccounts()) {
if (!account.isOptionSet(Account.OPTION_DISABLED)) { if (!account.isOptionSet(Account.OPTION_DISABLED)) {
sendPresence(account); sendPresence(account, includeIdleTimestamp);
} }
} }
} }

View file

@ -104,6 +104,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
} }
}; };
private Jid accountJid; private Jid accountJid;
private TextView lastseen;
private Jid contactJid; private Jid contactJid;
private TextView contactJidTv; private TextView contactJidTv;
private TextView accountJidTv; private TextView accountJidTv;
@ -114,7 +115,8 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
private QuickContactBadge badge; private QuickContactBadge badge;
private LinearLayout keys; private LinearLayout keys;
private LinearLayout tags; private LinearLayout tags;
private boolean showDynamicTags; private boolean showDynamicTags = false;
private boolean showLastSeen = false;
private String messageFingerprint; private String messageFingerprint;
private DialogInterface.OnClickListener addToPhonebook = new DialogInterface.OnClickListener() { private DialogInterface.OnClickListener addToPhonebook = new DialogInterface.OnClickListener() {
@ -203,6 +205,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
contactJidTv = (TextView) findViewById(R.id.details_contactjid); contactJidTv = (TextView) findViewById(R.id.details_contactjid);
accountJidTv = (TextView) findViewById(R.id.details_account); accountJidTv = (TextView) findViewById(R.id.details_account);
lastseen = (TextView) findViewById(R.id.details_lastseen);
statusMessage = (TextView) findViewById(R.id.status_message); statusMessage = (TextView) findViewById(R.id.status_message);
send = (CheckBox) findViewById(R.id.details_send_presence); send = (CheckBox) findViewById(R.id.details_send_presence);
receive = (CheckBox) findViewById(R.id.details_receive_presence); receive = (CheckBox) findViewById(R.id.details_receive_presence);
@ -220,9 +223,14 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
getActionBar().setHomeButtonEnabled(true); getActionBar().setHomeButtonEnabled(true);
getActionBar().setDisplayHomeAsUpEnabled(true); getActionBar().setDisplayHomeAsUpEnabled(true);
} }
}
@Override
public void onStart() {
super.onStart();
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
this.showDynamicTags = preferences.getBoolean("show_dynamic_tags",false); this.showDynamicTags = preferences.getBoolean("show_dynamic_tags",false);
this.showLastSeen = preferences.getBoolean("last_activity", false);
} }
@Override @Override
@ -371,6 +379,18 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
statusMessage.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) { if (contact.getPresences().size() > 1) {
contactJidTv.setText(contact.getDisplayJid() + " (" contactJidTv.setText(contact.getDisplayJid() + " ("
+ contact.getPresences().size() + ")"); + contact.getPresences().size() + ")");

View file

@ -162,7 +162,8 @@ public class SettingsActivity extends XmppActivity implements
"away_when_screen_off", "away_when_screen_off",
"allow_message_correction", "allow_message_correction",
"treat_vibrate_as_silent", "treat_vibrate_as_silent",
"manually_change_presence"); "manually_change_presence",
"last_activity");
if (name.equals("resource")) { if (name.equals("resource")) {
String resource = preferences.getString("resource", "mobile") String resource = preferences.getString("resource", "mobile")
.toLowerCase(Locale.US); .toLowerCase(Locale.US);

View file

@ -912,7 +912,7 @@ public abstract class XmppActivity extends Activity {
final String[] presencesArray = presences.asStringArray(); final String[] presencesArray = presences.asStringArray();
int preselectedPresence = 0; int preselectedPresence = 0;
for (int i = 0; i < presencesArray.length; ++i) { for (int i = 0; i < presencesArray.length; ++i) {
if (presencesArray[i].equals(contact.lastseen.presence)) { if (presencesArray[i].equals(contact.getLastPresence())) {
preselectedPresence = i; preselectedPresence = i;
break; break;
} }

View file

@ -107,12 +107,10 @@ public class UIHelper {
.get(Calendar.DAY_OF_YEAR); .get(Calendar.DAY_OF_YEAR);
} }
public static String lastseen(Context context, long time) { public static String lastseen(Context context, boolean active, long time) {
if (time == 0) {
return context.getString(R.string.never_seen);
}
long difference = (System.currentTimeMillis() - time) / 1000; long difference = (System.currentTimeMillis() - time) / 1000;
if (difference < 60) { active = active && difference <= 300;
if (active || difference < 60) {
return context.getString(R.string.last_seen_now); return context.getString(R.string.last_seen_now);
} else if (difference < 60 * 2) { } else if (difference < 60 * 2) {
return context.getString(R.string.last_seen_min); return context.getString(R.string.last_seen_min);

View file

@ -1,7 +1,5 @@
package eu.siacs.conversations.utils; package eu.siacs.conversations.utils;
import eu.siacs.conversations.Config;
public final class Xmlns { public final class Xmlns {
public static final String BLOCKING = "urn:xmpp:blocking"; public static final String BLOCKING = "urn:xmpp:blocking";
public static final String ROSTER = "jabber:iq:roster"; public static final String ROSTER = "jabber:iq:roster";

View file

@ -83,7 +83,7 @@ public class MessagePacket extends AbstractAcknowledgeableStanza {
if (packet == null) { if (packet == null) {
return null; return null;
} }
Long timestamp = AbstractParser.getTimestamp(forwarded,null); Long timestamp = AbstractParser.parseTimestamp(forwarded, null);
return new Pair(packet,timestamp); return new Pair(packet,timestamp);
} }

View file

@ -53,12 +53,20 @@
android:orientation="horizontal"> android:orientation="horizontal">
</LinearLayout> </LinearLayout>
<TextView
android:id="@+id/details_lastseen"
android:layout_marginTop="4dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black54"
android:textSize="?attr/TextSizeBody" />
<TextView <TextView
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:id="@+id/status_message" android:id="@+id/status_message"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="@color/black54" android:textColor="@color/black87"
android:textStyle="italic" android:textStyle="italic"
android:textSize="?attr/TextSizeBody" /> android:textSize="?attr/TextSizeBody" />

View file

@ -653,4 +653,7 @@
<string name="gp_short">Short</string> <string name="gp_short">Short</string>
<string name="gp_medium">Medium</string> <string name="gp_medium">Medium</string>
<string name="gp_long">Long</string> <string name="gp_long">Long</string>
<string name="pref_broadcast_last_activity">Broadcast last activity</string>
<string name="pref_broadcast_last_activity_summary">Let all your contacts know when use Conversations</string>
<string name="pref_privacy">Privacy</string>
</resources> </resources>

View file

@ -15,6 +15,8 @@
android:key="resource" android:key="resource"
android:summary="@string/pref_xmpp_resource_summary" android:summary="@string/pref_xmpp_resource_summary"
android:title="@string/pref_xmpp_resource"/> android:title="@string/pref_xmpp_resource"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/pref_privacy">
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="true" android:defaultValue="true"
android:key="confirm_messages" android:key="confirm_messages"
@ -26,11 +28,13 @@
android:key="chat_states" android:key="chat_states"
android:summary="@string/pref_chat_states_summary" android:summary="@string/pref_chat_states_summary"
android:title="@string/pref_chat_states"/> android:title="@string/pref_chat_states"/>
<CheckBoxPreference
android:defaultValue="false"
android:key="last_activity"
android:title="@string/pref_broadcast_last_activity"
android:summary="@string/pref_broadcast_last_activity_summary"/>
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory android:title="@string/pref_notification_settings">
android:key="notifications"
android:title="@string/pref_notification_settings">
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="true" android:defaultValue="true"
android:key="show_notification" android:key="show_notification"
@ -88,8 +92,7 @@
android:entryValues="@array/grace_periods_values" android:entryValues="@array/grace_periods_values"
/> />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory android:title="@string/pref_attachments">
android:title="@string/pref_attachments">
<ListPreference <ListPreference
android:defaultValue="524288" android:defaultValue="524288"
android:entries="@array/filesizes" android:entries="@array/filesizes"