summaryrefslogtreecommitdiffstats
blob: 7ac5b9d2aeb147f2ddd49820de7f1f7061155125 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/*
 * Copyright (c) 2010-2016, Isode Limited, London, England.
 * All rights reserved.
 */
package com.isode.stroke.presence;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import com.isode.stroke.client.StanzaChannel;
import com.isode.stroke.elements.Presence;
import com.isode.stroke.elements.StatusShow;
import com.isode.stroke.jid.JID;
import com.isode.stroke.roster.XMPPRoster;
import com.isode.stroke.signals.Signal1;
import com.isode.stroke.signals.SignalConnection;
import com.isode.stroke.signals.Slot1;

public class PresenceOracle {
	private final Map<JID,Map<JID,Presence>> entries_ = new HashMap<JID,Map<JID,Presence>>();
	private final StanzaChannel stanzaChannel_;
	private final SignalConnection onPresenceReceivedSignal;
	private final SignalConnection onAvailableChangedSignal;
	private final XMPPRoster xmppRoster_;


	public final Signal1<Presence> onPresenceChange = new Signal1<Presence>();
    private final SignalConnection onJIDRemovedConnection;

	public PresenceOracle(StanzaChannel stanzaChannel, XMPPRoster xmppRoster) {
	    stanzaChannel_ = stanzaChannel;
	    xmppRoster_ = xmppRoster;
	    onPresenceReceivedSignal = stanzaChannel_.onPresenceReceived.connect(new Slot1<Presence>() {
	        @Override
	        public void call(Presence p1) {
	            handleIncomingPresence(p1);
	        }
	    });
	    onAvailableChangedSignal = stanzaChannel_.onAvailableChanged.connect(new Slot1<Boolean>() {
	        @Override
	        public void call(Boolean p1) {
	            handleStanzaChannelAvailableChanged(p1);
	        }
	    });
	    onJIDRemovedConnection = xmppRoster_.onJIDRemoved.connect(new Slot1<JID>() {

	        @Override
	        public void call(JID removedJID) {
	            handleJIDRemoved(removedJID);
	        }

	    });
	}

	void delete() {
		onPresenceReceivedSignal.disconnect();
		onAvailableChangedSignal.disconnect();
		onJIDRemovedConnection.disconnect();
	}

	void handleStanzaChannelAvailableChanged(boolean available) {
		if (available) {
			entries_.clear();
		}
	}


	void handleIncomingPresence(Presence presence) {
		JID bareJID = presence.getFrom().toBare();
		if (Presence.Type.Subscribe.equals(presence.getType())) {
		}
		else {
			Presence passedPresence = presence;
			if (presence.getType() == Presence.Type.Unsubscribe) {
				/* 3921bis says that we don't follow up with an unavailable, so simulate this ourselves */
				passedPresence = new Presence();
				passedPresence.setType(Presence.Type.Unavailable);
				passedPresence.setFrom(bareJID);
				passedPresence.setStatus(presence.getStatus());
			}
			Map<JID,Presence> jidMap = entries_.get(bareJID);
			if (jidMap == null) jidMap = new HashMap<JID,Presence>();
			if (passedPresence.getFrom().isBare() && Presence.Type.Unavailable.equals(presence.getType())) {
				/* Have a bare-JID only presence of offline */
				jidMap.clear();
			} else if (Presence.Type.Available.equals(passedPresence.getType())) {
				/* Don't have a bare-JID only offline presence once there are available presences */
				jidMap.remove(bareJID);
			}
			if (Presence.Type.Unavailable.equals(passedPresence.getType()) && jidMap.size() > 1) {
				jidMap.remove(passedPresence.getFrom());
			} else {
				jidMap.put(passedPresence.getFrom(), passedPresence);
			}
			entries_.put(bareJID, jidMap);
			onPresenceChange.emit(passedPresence);
		}
	}
	
	private void handleJIDRemoved(JID removedJID) {
	    // 3921bis says that we don't follow up with an unavailable, so simulate this ourselves
	    Presence unavailablePresence = new Presence();
	    unavailablePresence.setType(Presence.Type.Unavailable);
	    unavailablePresence.setFrom(removedJID);

	    if (entries_.containsKey(removedJID.toBare())) {
	        Map<JID,Presence> presenceMap = entries_.get(removedJID.toBare());
	        presenceMap.clear();
	        presenceMap.put(removedJID, unavailablePresence);
	    }

	    onPresenceChange.emit(unavailablePresence);
	}

	public Presence getLastPresence(final JID jid) {
		Map<JID,Presence> presenceMap = entries_.get(jid.toBare());
		if (presenceMap == null) return null;
		
		Presence i = presenceMap.get(jid);
		if (i != null) {
			return i;
		} else {
			return null;
		}
	}

	public Collection<Presence> getAllPresence(final JID bareJID) {
		Collection<Presence> results = new ArrayList<Presence>();
		
		Map<JID,Presence> presenceMap = entries_.get(bareJID);
		if (presenceMap == null) return results;
		
		results.addAll(presenceMap.values());
		return results;
	}

	public Presence getHighestPriorityPresence(final JID bareJID) {
		Map<JID,Presence> presenceMap = entries_.get(bareJID);
		if (presenceMap == null) return null;

		Presence highest = null;
		for (Presence current : presenceMap.values()) {
			if (highest == null
					|| current.getPriority() > highest.getPriority()
					|| (current.getPriority() == highest.getPriority()
							&& StatusShow.typeToAvailabilityOrdering(current.getShow()) > StatusShow.typeToAvailabilityOrdering(highest.getShow()))) {
				highest = current;
			}

		}
		return highest;
	}
}