1package eu.siacs.conversations.xmpp.manager;
2
3import android.content.Context;
4import eu.siacs.conversations.services.XmppConnectionService;
5import android.util.Log;
6import com.google.common.base.Strings;
7import eu.siacs.conversations.AppSettings;
8import eu.siacs.conversations.Config;
9import eu.siacs.conversations.android.Device;
10import eu.siacs.conversations.generator.AbstractGenerator;
11import eu.siacs.conversations.services.XmppConnectionService;
12import eu.siacs.conversations.xml.Namespace;
13import eu.siacs.conversations.xmpp.Jid;
14import eu.siacs.conversations.xmpp.XmppConnection;
15import im.conversations.android.xmpp.EntityCapabilities;
16import im.conversations.android.xmpp.EntityCapabilities2;
17import im.conversations.android.xmpp.ServiceDescription;
18import im.conversations.android.xmpp.model.Extension;
19import im.conversations.android.xmpp.model.capabilties.Capabilities;
20import im.conversations.android.xmpp.model.capabilties.LegacyCapabilities;
21import im.conversations.android.xmpp.model.nick.Nick;
22import im.conversations.android.xmpp.model.pars.PreAuth;
23import im.conversations.android.xmpp.model.pgp.Signed;
24import im.conversations.android.xmpp.model.stanza.Presence;
25import java.util.HashMap;
26import java.util.Map;
27
28public class PresenceManager extends AbstractManager {
29
30 private final XmppConnectionService service;
31 private final AppSettings appSettings;
32
33 private final Map<EntityCapabilities.Hash, ServiceDescription> serviceDescriptions =
34 new HashMap<>();
35
36 public PresenceManager(final XmppConnectionService service, final XmppConnection connection) {
37 super(service, connection);
38 this.appSettings = new AppSettings(service.getApplicationContext());
39 this.service = service;
40 }
41
42 public void subscribe(final Jid address) {
43 subscribe(address, null);
44 }
45
46 public void subscribe(final Jid address, final String preAuth) {
47
48 var presence = new Presence(Presence.Type.SUBSCRIBE);
49 presence.setTo(address);
50
51 final var displayName = getAccount().getDisplayName();
52 if (!Strings.isNullOrEmpty(displayName)) {
53 presence.addExtension(new Nick(displayName));
54 }
55 if (preAuth != null) {
56 presence.addExtension(new PreAuth()).setToken(preAuth);
57 }
58 this.connection.sendPresencePacket(presence);
59 }
60
61 public void unsubscribe(final Jid address) {
62 var presence = new Presence(Presence.Type.UNSUBSCRIBE);
63 presence.setTo(address);
64 this.connection.sendPresencePacket(presence);
65 }
66
67 public void unsubscribed(final Jid address) {
68 var presence = new Presence(Presence.Type.UNSUBSCRIBED);
69 presence.setTo(address);
70 this.connection.sendPresencePacket(presence);
71 }
72
73 public void subscribed(final Jid address) {
74 var presence = new Presence(Presence.Type.SUBSCRIBED);
75 presence.setTo(address);
76 this.connection.sendPresencePacket(presence);
77 }
78
79 public void available() {
80 available(service.checkListeners() && appSettings.isBroadcastLastActivity());
81 }
82
83 public void available(final boolean withIdle) {
84 final var account = connection.getAccount();
85 final var serviceDiscoveryFeatures = getManager(DiscoManager.class).getServiceDescription();
86 final var infoQuery = serviceDiscoveryFeatures.asInfoQuery();
87 final var capsHash = EntityCapabilities.hash(infoQuery);
88 final var caps2Hash = EntityCapabilities2.hash(infoQuery);
89 serviceDescriptions.put(capsHash, serviceDiscoveryFeatures);
90 serviceDescriptions.put(caps2Hash, serviceDiscoveryFeatures);
91 final var capabilities = new Capabilities();
92 capabilities.setHash(caps2Hash);
93 final var legacyCapabilities = new LegacyCapabilities();
94 legacyCapabilities.setNode(DiscoManager.CAPABILITY_NODE);
95 legacyCapabilities.setHash(capsHash);
96 final var presence = new Presence();
97 presence.addExtension(capabilities);
98 presence.addExtension(legacyCapabilities);
99 final String pgpSignature = account.getPgpSignature();
100 final String message = account.getPresenceStatusMessage();
101 final Presence.Availability availability;
102 if (appSettings.isUserManagedAvailability()) {
103 availability = account.getPresenceStatus();
104 } else {
105 availability = getTargetPresence();
106 }
107 presence.setAvailability(availability);
108 presence.setStatus(message);
109 if (pgpSignature != null) {
110 presence.addExtension(new Signed(pgpSignature));
111 }
112
113 final var lastActivity = service.getLastActivity();
114 if (lastActivity > 0 && withIdle) {
115 final long since =
116 Math.min(lastActivity, System.currentTimeMillis()); // don't send future dates
117 presence.addChild("idle", Namespace.IDLE)
118 .setAttribute("since", AbstractGenerator.getTimestamp(since));
119 }
120 Log.d(Config.LOGTAG, "--> " + presence);
121 connection.sendPresencePacket(presence);
122 }
123
124 public void unavailable() {
125 var presence = new Presence(Presence.Type.UNAVAILABLE);
126 this.connection.sendPresencePacket(presence);
127 }
128
129 public void available(final Jid to, final Extension... extensions) {
130 available(to, null, extensions);
131 }
132
133 public void available(final Jid to, final String message, final Extension... extensions) {
134 final var presence = new Presence();
135 presence.setTo(to);
136 presence.setStatus(message);
137 for (final var extension : extensions) {
138 presence.addExtension(extension);
139 }
140 connection.sendPresencePacket(presence);
141 }
142
143 public void unavailable(final Jid to) {
144 final var presence = new Presence(Presence.Type.UNAVAILABLE);
145 presence.setTo(to);
146 connection.sendPresencePacket(presence);
147 }
148
149 private im.conversations.android.xmpp.model.stanza.Presence.Availability getTargetPresence() {
150 final var device = new Device(context);
151 if (appSettings.isDndOnSilentMode()
152 && device.isPhoneSilenced(appSettings.isTreatVibrateAsSilent())) {
153 return im.conversations.android.xmpp.model.stanza.Presence.Availability.DND;
154 } else if (appSettings.isAwayWhenScreenLocked() && device.isScreenLocked()) {
155 return im.conversations.android.xmpp.model.stanza.Presence.Availability.AWAY;
156 } else {
157 return im.conversations.android.xmpp.model.stanza.Presence.Availability.ONLINE;
158 }
159 }
160
161 public Presence getPresence(final Presence.Availability availability, final boolean personal) {
162 final var account = connection.getAccount();
163 final var serviceDiscoveryFeatures = getManager(DiscoManager.class).getServiceDescription();
164 final var infoQuery = serviceDiscoveryFeatures.asInfoQuery();
165 final var capsHash = EntityCapabilities.hash(infoQuery);
166 final var caps2Hash = EntityCapabilities2.hash(infoQuery);
167 serviceDescriptions.put(capsHash, serviceDiscoveryFeatures);
168 serviceDescriptions.put(caps2Hash, serviceDiscoveryFeatures);
169 final var capabilities = new Capabilities();
170 capabilities.setHash(caps2Hash);
171 final var legacyCapabilities = new LegacyCapabilities();
172 legacyCapabilities.setNode(DiscoManager.CAPABILITY_NODE);
173 legacyCapabilities.setHash(capsHash);
174 final var presence = new Presence();
175 presence.addExtension(capabilities);
176 presence.addExtension(legacyCapabilities);
177
178 if (personal) {
179 final String pgpSignature = account.getPgpSignature();
180 final String message = account.getPresenceStatusMessage();
181 presence.setAvailability(availability);
182 presence.setStatus(message);
183 if (pgpSignature != null) {
184 final var signed = new Signed();
185 signed.setContent(pgpSignature);
186 presence.addExtension(new Signed());
187 }
188 }
189 return presence;
190 }
191
192 public ServiceDescription getCachedServiceDescription(final EntityCapabilities.Hash hash) {
193 return this.serviceDescriptions.get(hash);
194 }
195}