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 final var presence = new Presence(Presence.Type.SUBSCRIBE);
48 presence.setTo(address);
49
50 final var displayName = getAccount().getDisplayName();
51 if (!Strings.isNullOrEmpty(displayName)) {
52 presence.addExtension(new Nick(displayName));
53 }
54 if (preAuth != null) {
55 presence.addExtension(new PreAuth()).setToken(preAuth);
56 }
57 this.connection.sendPresencePacket(presence);
58 }
59
60 public void unsubscribe(final Jid address) {
61 final var presence = new Presence(Presence.Type.UNSUBSCRIBE);
62 presence.setTo(address);
63 this.connection.sendPresencePacket(presence);
64 }
65
66 public void unsubscribed(final Jid address) {
67 final var presence = new Presence(Presence.Type.UNSUBSCRIBED);
68 presence.setTo(address);
69 this.connection.sendPresencePacket(presence);
70 }
71
72 public void subscribed(final Jid address) {
73 var presence = new Presence(Presence.Type.SUBSCRIBED);
74 presence.setTo(address);
75 this.connection.sendPresencePacket(presence);
76 }
77
78 public void available() {
79 available(service.checkListeners() && appSettings.isBroadcastLastActivity());
80 }
81
82 public void available(final boolean withIdle) {
83 final var account = connection.getAccount();
84 final var serviceDiscoveryFeatures = getManager(DiscoManager.class).getServiceDescription();
85 final var infoQuery = serviceDiscoveryFeatures.asInfoQuery();
86 final var capsHash = EntityCapabilities.hash(infoQuery);
87 final var caps2Hash = EntityCapabilities2.hash(infoQuery);
88 serviceDescriptions.put(capsHash, serviceDiscoveryFeatures);
89 serviceDescriptions.put(caps2Hash, serviceDiscoveryFeatures);
90 final var capabilities = new Capabilities();
91 capabilities.setHash(caps2Hash);
92 final var legacyCapabilities = new LegacyCapabilities();
93 legacyCapabilities.setNode(DiscoManager.CAPABILITY_NODE);
94 legacyCapabilities.setHash(capsHash);
95 final var presence = new Presence();
96 presence.addExtension(capabilities);
97 presence.addExtension(legacyCapabilities);
98 final String pgpSignature = account.getPgpSignature();
99 final String message = account.getPresenceStatusMessage();
100 final Presence.Availability availability;
101 if (appSettings.isUserManagedAvailability()) {
102 availability = account.getPresenceStatus();
103 } else {
104 availability = getTargetPresence();
105 }
106 presence.setAvailability(availability);
107 presence.setStatus(message);
108 if (pgpSignature != null) {
109 presence.addExtension(new Signed(pgpSignature));
110 }
111
112 final var lastActivity = service.getLastActivity();
113 if (lastActivity > 0 && withIdle) {
114 final long since =
115 Math.min(lastActivity, System.currentTimeMillis()); // don't send future dates
116 presence.addChild("idle", Namespace.IDLE)
117 .setAttribute("since", AbstractGenerator.getTimestamp(since));
118 }
119 Log.d(Config.LOGTAG, "--> " + presence);
120 connection.sendPresencePacket(presence);
121 }
122
123 public void unavailable() {
124 var presence = new Presence(Presence.Type.UNAVAILABLE);
125 this.connection.sendPresencePacket(presence);
126 }
127
128 public void available(final Jid to, final Extension... extensions) {
129 available(to, null, extensions);
130 }
131
132 public void available(final Jid to, final String message, final Extension... extensions) {
133 final var presence = new Presence();
134 presence.setTo(to);
135 presence.setStatus(message);
136 for (final var extension : extensions) {
137 presence.addExtension(extension);
138 }
139 connection.sendPresencePacket(presence);
140 }
141
142 public void unavailable(final Jid to) {
143 final var presence = new Presence(Presence.Type.UNAVAILABLE);
144 presence.setTo(to);
145 connection.sendPresencePacket(presence);
146 }
147
148 private im.conversations.android.xmpp.model.stanza.Presence.Availability getTargetPresence() {
149 final var device = new Device(context);
150 if (appSettings.isDndOnSilentMode()
151 && device.isPhoneSilenced(appSettings.isTreatVibrateAsSilent())) {
152 return im.conversations.android.xmpp.model.stanza.Presence.Availability.DND;
153 } else if (appSettings.isAwayWhenScreenLocked() && device.isScreenLocked()) {
154 return im.conversations.android.xmpp.model.stanza.Presence.Availability.AWAY;
155 } else {
156 return im.conversations.android.xmpp.model.stanza.Presence.Availability.ONLINE;
157 }
158 }
159
160 public ServiceDescription getCachedServiceDescription(final EntityCapabilities.Hash hash) {
161 return this.serviceDescriptions.get(hash);
162 }
163}