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