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 final var signed = new Signed();
109 signed.setContent(pgpSignature);
110 presence.addExtension(signed);
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 final var presence = new Presence();
131 presence.setTo(to);
132 for (final var extension : extensions) {
133 presence.addExtension(extension);
134 }
135 connection.sendPresencePacket(presence);
136 }
137
138 public void unavailable(final Jid to) {
139 final var presence = new Presence(Presence.Type.UNAVAILABLE);
140 presence.setTo(to);
141 connection.sendPresencePacket(presence);
142 }
143
144 private im.conversations.android.xmpp.model.stanza.Presence.Availability getTargetPresence() {
145 final var device = new Device(context);
146 if (appSettings.isDndOnSilentMode()
147 && device.isPhoneSilenced(appSettings.isTreatVibrateAsSilent())) {
148 return im.conversations.android.xmpp.model.stanza.Presence.Availability.DND;
149 } else if (appSettings.isAwayWhenScreenLocked() && device.isScreenLocked()) {
150 return im.conversations.android.xmpp.model.stanza.Presence.Availability.AWAY;
151 } else {
152 return im.conversations.android.xmpp.model.stanza.Presence.Availability.ONLINE;
153 }
154 }
155
156 public Presence getPresence(final Presence.Availability availability, final boolean personal) {
157 final var account = connection.getAccount();
158 final var serviceDiscoveryFeatures = getManager(DiscoManager.class).getServiceDescription();
159 final var infoQuery = serviceDiscoveryFeatures.asInfoQuery();
160 final var capsHash = EntityCapabilities.hash(infoQuery);
161 final var caps2Hash = EntityCapabilities2.hash(infoQuery);
162 serviceDescriptions.put(capsHash, serviceDiscoveryFeatures);
163 serviceDescriptions.put(caps2Hash, serviceDiscoveryFeatures);
164 final var capabilities = new Capabilities();
165 capabilities.setHash(caps2Hash);
166 final var legacyCapabilities = new LegacyCapabilities();
167 legacyCapabilities.setNode(DiscoManager.CAPABILITY_NODE);
168 legacyCapabilities.setHash(capsHash);
169 final var presence = new Presence();
170 presence.addExtension(capabilities);
171 presence.addExtension(legacyCapabilities);
172
173 if (personal) {
174 final String pgpSignature = account.getPgpSignature();
175 final String message = account.getPresenceStatusMessage();
176 presence.setAvailability(availability);
177 presence.setStatus(message);
178 if (pgpSignature != null) {
179 final var signed = new Signed();
180 signed.setContent(pgpSignature);
181 presence.addExtension(new Signed());
182 }
183 }
184 return presence;
185 }
186
187 public ServiceDescription getCachedServiceDescription(final EntityCapabilities.Hash hash) {
188 return this.serviceDescriptions.get(hash);
189 }
190}