1package eu.siacs.conversations.services;
2
3import android.util.Log;
4
5import com.google.android.gms.common.ConnectionResult;
6import com.google.android.gms.common.GoogleApiAvailability;
7import com.google.firebase.iid.FirebaseInstanceId;
8import com.google.firebase.iid.InstanceIdResult;
9
10import eu.siacs.conversations.Config;
11import eu.siacs.conversations.R;
12import eu.siacs.conversations.entities.Account;
13import eu.siacs.conversations.entities.Conversation;
14import eu.siacs.conversations.utils.PhoneHelper;
15import eu.siacs.conversations.xml.Element;
16import eu.siacs.conversations.xml.Namespace;
17import eu.siacs.conversations.xmpp.OnIqPacketReceived;
18import eu.siacs.conversations.xmpp.XmppConnection;
19import eu.siacs.conversations.xmpp.forms.Data;
20import eu.siacs.conversations.xmpp.stanzas.IqPacket;
21import rocks.xmpp.addr.Jid;
22
23public class PushManagementService {
24
25 protected final XmppConnectionService mXmppConnectionService;
26
27 PushManagementService(XmppConnectionService service) {
28 this.mXmppConnectionService = service;
29 }
30
31 private static Data findResponseData(IqPacket response) {
32 final Element command = response.findChild("command", Namespace.COMMANDS);
33 final Element x = command == null ? null : command.findChild("x", Namespace.DATA);
34 return x == null ? null : Data.parse(x);
35 }
36
37 private Jid getAppServer() {
38 return Jid.of(mXmppConnectionService.getString(R.string.app_server));
39 }
40
41 void registerPushTokenOnServer(final Account account) {
42 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": has push support");
43 retrieveFcmInstanceToken(token -> {
44 final String androidId = PhoneHelper.getAndroidId(mXmppConnectionService);
45 final IqPacket packet = mXmppConnectionService.getIqGenerator().pushTokenToAppServer(getAppServer(), token, androidId);
46 mXmppConnectionService.sendIqPacket(account, packet, (a, response) -> {
47 final Data data = findResponseData(response);
48 if (response.getType() == IqPacket.TYPE.RESULT && data != null) {
49 try {
50 String node = data.getValue("node");
51 String secret = data.getValue("secret");
52 Jid jid = Jid.of(data.getValue("jid"));
53 if (node != null && secret != null) {
54 enablePushOnServer(a, jid, node, secret);
55 }
56 } catch (IllegalArgumentException e) {
57 e.printStackTrace();
58 }
59 } else {
60 Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": invalid response from app server");
61 }
62 });
63 });
64 }
65
66 public void unregisterChannel(final Account account, final String channel) {
67 final String androidId = PhoneHelper.getAndroidId(mXmppConnectionService);
68 final Jid appServer = getAppServer();
69 final IqPacket packet = mXmppConnectionService.getIqGenerator().unregisterChannelOnAppServer(appServer, androidId, channel);
70 mXmppConnectionService.sendIqPacket(account, packet, (a, response) -> {
71 if (response.getType() == IqPacket.TYPE.RESULT) {
72 Log.d(Config.LOGTAG,a.getJid().asBareJid()+": successfully unregistered channel");
73 } else if (response.getType() == IqPacket.TYPE.ERROR) {
74 Log.d(Config.LOGTAG, a.getJid().asBareJid()+": unable to unregister channel with hash "+channel);
75 }
76 });
77 }
78
79 void registerPushTokenOnServer(final Conversation conversation) {
80 Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": room "+conversation.getJid().asBareJid()+" has push support");
81 retrieveFcmInstanceToken(token -> {
82 final Jid muc = conversation.getJid().asBareJid();
83 final String androidId = PhoneHelper.getAndroidId(mXmppConnectionService);
84 final IqPacket packet = mXmppConnectionService.getIqGenerator().pushTokenToAppServer(getAppServer(), token, androidId, muc);
85 packet.setTo(muc);
86 mXmppConnectionService.sendIqPacket(conversation.getAccount(), packet, (a, response) -> {
87 final Data data = findResponseData(response);
88 if (response.getType() == IqPacket.TYPE.RESULT && data != null) {
89 try {
90 final String node = data.getValue("node");
91 final String secret = data.getValue("secret");
92 final Jid jid = Jid.of(data.getValue("jid"));
93 if (node != null && secret != null) {
94 enablePushOnServer(conversation, jid, node, secret);
95 }
96 } catch (IllegalArgumentException e) {
97 e.printStackTrace();
98 }
99 } else {
100 Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": invalid response from app server");
101 }
102 });
103 });
104 }
105
106 private void enablePushOnServer(final Account account, final Jid appServer, final String node, final String secret) {
107 final IqPacket enable = mXmppConnectionService.getIqGenerator().enablePush(appServer, node, secret);
108 mXmppConnectionService.sendIqPacket(account, enable, (a, p) -> {
109 if (p.getType() == IqPacket.TYPE.RESULT) {
110 Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": successfully enabled push on server");
111 } else if (p.getType() == IqPacket.TYPE.ERROR) {
112 Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": enabling push on server failed");
113 }
114 });
115 }
116
117 private void enablePushOnServer(final Conversation conversation, final Jid appServer, final String node, final String secret) {
118 final Jid muc = conversation.getJid().asBareJid();
119 final IqPacket enable = mXmppConnectionService.getIqGenerator().enablePush(appServer, node, secret);
120 enable.setTo(muc);
121 mXmppConnectionService.sendIqPacket(conversation.getAccount(), enable, (a, p) -> {
122 if (p.getType() == IqPacket.TYPE.RESULT) {
123 Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": successfully enabled push on " + muc);
124 if (conversation.setAttribute(Conversation.ATTRIBUTE_ALWAYS_NOTIFY, node)) {
125 mXmppConnectionService.updateConversation(conversation);
126 }
127 } else if (p.getType() == IqPacket.TYPE.ERROR) {
128 Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": enabling push on " + muc + " failed");
129 }
130 });
131 }
132
133 public void disablePushOnServer(final Conversation conversation) {
134 final Jid muc = conversation.getJid().asBareJid();
135 final String node = conversation.getAttribute(Conversation.ATTRIBUTE_PUSH_NODE);
136 if (node != null) {
137 final IqPacket disable = mXmppConnectionService.getIqGenerator().disablePush(getAppServer(), node);
138 disable.setTo(muc);
139 mXmppConnectionService.sendIqPacket(conversation.getAccount(), disable, (account, response) -> {
140 if (response.getType() == IqPacket.TYPE.ERROR) {
141 Log.d(Config.LOGTAG,account.getJid().asBareJid()+": unable to disable push for room "+muc);
142 }
143 });
144 } else {
145 Log.d(Config.LOGTAG,conversation.getAccount().getJid().asBareJid()+": room "+muc+" has no stored node. unable to disable push");
146 }
147 }
148
149 private void retrieveFcmInstanceToken(final OnGcmInstanceTokenRetrieved instanceTokenRetrieved) {
150 FirebaseInstanceId.getInstance().getInstanceId().addOnCompleteListener(task -> {
151 if (!task.isSuccessful()) {
152 Log.d(Config.LOGTAG, "unable to get Firebase instance token", task.getException());
153 }
154 final InstanceIdResult result;
155 try {
156 result = task.getResult();
157 } catch (Exception e) {
158 Log.d(Config.LOGTAG, "unable to get Firebase instance token due to bug in library ", e);
159 return;
160 }
161 if (result != null) {
162 instanceTokenRetrieved.onGcmInstanceTokenRetrieved(result.getToken());
163 }
164 });
165
166 }
167
168
169 public boolean available(Account account) {
170 final XmppConnection connection = account.getXmppConnection();
171 return connection != null
172 && connection.getFeatures().sm()
173 && connection.getFeatures().push()
174 && playServicesAvailable();
175 }
176
177 private boolean playServicesAvailable() {
178 return GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(mXmppConnectionService) == ConnectionResult.SUCCESS;
179 }
180
181 public boolean isStub() {
182 return false;
183 }
184
185 interface OnGcmInstanceTokenRetrieved {
186 void onGcmInstanceTokenRetrieved(String token);
187 }
188}