From 7809af9b571ab8e7f6747eff9e25d6dd778e14d9 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 25 Jun 2019 18:15:51 +0200 Subject: [PATCH] implement FCM push for group chats --- .../services/PushManagementService.java | 11 +- .../conversations/entities/Conversation.java | 1 + .../conversations/generator/IqGenerator.java | 15 +- .../services/XmppConnectionService.java | 26 ++- .../eu/siacs/conversations/xml/Namespace.java | 1 + .../services/PushManagementService.java | 205 ++++++++++++------ 6 files changed, 176 insertions(+), 83 deletions(-) diff --git a/src/free/java/eu/siacs/conversations/services/PushManagementService.java b/src/free/java/eu/siacs/conversations/services/PushManagementService.java index ffce90b8a..b1cbe6e28 100644 --- a/src/free/java/eu/siacs/conversations/services/PushManagementService.java +++ b/src/free/java/eu/siacs/conversations/services/PushManagementService.java @@ -1,6 +1,7 @@ package eu.siacs.conversations.services; import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Conversation; public class PushManagementService { @@ -10,7 +11,15 @@ public class PushManagementService { this.mXmppConnectionService = service; } - public void registerPushTokenOnServer(Account account) { + void registerPushTokenOnServer(Account account) { + //stub implementation. only affects playstore flavor + } + + void registerPushTokenOnServer(Conversation conversation) { + //stub implementation. only affects playstore flavor + } + + void disablePushOnServer(Conversation conversation) { //stub implementation. only affects playstore flavor } diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 8b359713a..c9ec5637a 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -54,6 +54,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl public static final String ATTRIBUTE_MUTED_TILL = "muted_till"; public static final String ATTRIBUTE_ALWAYS_NOTIFY = "always_notify"; + public static final String ATTRIBUTE_PUSH_NODE = "push_node"; public static final String ATTRIBUTE_LAST_CLEAR_HISTORY = "last_clear_history"; static final String ATTRIBUTE_MUC_PASSWORD = "muc_password"; private static final String ATTRIBUTE_NEXT_MESSAGE = "next_message"; diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index af5b2cd39..40742e231 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -423,14 +423,21 @@ public class IqGenerator extends AbstractGenerator { } public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId) { - IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + return pushTokenToAppServer(appServer, token, deviceId, null); + } + + public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId, Jid muc) { + final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); packet.setTo(appServer); - Element command = packet.addChild("command", "http://jabber.org/protocol/commands"); + final Element command = packet.addChild("command", Namespace.COMMANDS); command.setAttribute("node", "register-push-fcm"); command.setAttribute("action", "execute"); - Data data = new Data(); + final Data data = new Data(); data.put("token", token); data.put("android-id", deviceId); + if (muc != null) { + data.put("muc", muc.toEscapedString()); + } data.submit(); command.addChild(data); return packet; @@ -454,7 +461,7 @@ public class IqGenerator extends AbstractGenerator { public IqPacket disablePush(final Jid jid, final String node) { IqPacket packet = new IqPacket(IqPacket.TYPE.SET); Element disable = packet.addChild("disable", Namespace.PUSH); - disable.setAttribute("jid", jid.toString()); + disable.setAttribute("jid", jid.toEscapedString()); disable.setAttribute("node", node); return packet; } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 75fa9b699..9a8c7e62a 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -2583,31 +2583,35 @@ public class XmppConnectionService extends Service { } } - private void enableMucPush(final Conversation conversation) { - final Account account = conversation.getAccount(); - final Jid room = conversation.getJid().asBareJid(); + private void enableDirectMucPush(final Conversation conversation) { + final Account account = conversation.getAccount(); + final Jid room = conversation.getJid().asBareJid(); final IqPacket enable = mIqGenerator.enablePush(conversation.getAccount().getJid(), conversation.getUuid(), null); enable.setTo(room); sendIqPacket(account, enable, (a, response) -> { if (response.getType() == IqPacket.TYPE.RESULT) { - Log.d(Config.LOGTAG,a.getJid().asBareJid()+": enabled push for muc "+room); + Log.d(Config.LOGTAG,a.getJid().asBareJid()+": enabled direct push for muc "+room); } else if (response.getType() == IqPacket.TYPE.ERROR) { - Log.d(Config.LOGTAG,a.getJid().asBareJid()+": unable to enable push for muc "+room+" "+response.getError()); + Log.d(Config.LOGTAG,a.getJid().asBareJid()+": unable to enable direct push for muc "+room+" "+response.getError()); } }); - } - private void disableMucPush(final Conversation conversation) { + private void enableMucPush(final Conversation conversation) { + enableDirectMucPush(conversation); + mPushManagementService.registerPushTokenOnServer(conversation); + } + + private void disableDirectMucPush(final Conversation conversation) { final Account account = conversation.getAccount(); final Jid room = conversation.getJid().asBareJid(); final IqPacket disable = mIqGenerator.disablePush(conversation.getAccount().getJid(), conversation.getUuid()); disable.setTo(room); sendIqPacket(account, disable, (a, response) -> { if (response.getType() == IqPacket.TYPE.RESULT) { - Log.d(Config.LOGTAG,a.getJid().asBareJid()+": disabled push for muc "+room); + Log.d(Config.LOGTAG,a.getJid().asBareJid()+": disabled direct push for muc "+room); } else if (response.getType() == IqPacket.TYPE.ERROR) { - Log.d(Config.LOGTAG,a.getJid().asBareJid()+": unable to disable push for muc "+room+" "+response.getError()); + Log.d(Config.LOGTAG,a.getJid().asBareJid()+": unable to disable direct push for muc "+room+" "+response.getError()); } }); } @@ -2792,7 +2796,8 @@ public class XmppConnectionService extends Service { account.pendingConferenceLeaves.remove(conversation); if (account.getStatus() == Account.State.ONLINE || now) { if (conversation.getMucOptions().push()) { - disableMucPush(conversation); + disableDirectMucPush(conversation); + mPushManagementService.disablePushOnServer(conversation); } sendPresencePacket(conversation.getAccount(), mPresenceGenerator.leave(conversation.getMucOptions())); conversation.getMucOptions().setOffline(); @@ -4063,6 +4068,7 @@ public class XmppConnectionService extends Service { for (Account account : getAccounts()) { if (account.isOnlineAndConnected() && mPushManagementService.available(account)) { mPushManagementService.registerPushTokenOnServer(account); + //TODO renew mucs } } } diff --git a/src/main/java/eu/siacs/conversations/xml/Namespace.java b/src/main/java/eu/siacs/conversations/xml/Namespace.java index cfc20b1a2..dbab039d7 100644 --- a/src/main/java/eu/siacs/conversations/xml/Namespace.java +++ b/src/main/java/eu/siacs/conversations/xml/Namespace.java @@ -29,4 +29,5 @@ public final class Namespace { public static final String JINGLE_TRANSPORTS_IBB = "urn:xmpp:jingle:transports:ibb:1"; public static final String PING = "urn:xmpp:ping"; public static final String PUSH = "urn:xmpp:push:0"; + public static final String COMMANDS = "http://jabber.org/protocol/commands"; } diff --git a/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java b/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java index e85898688..e500a1ac3 100644 --- a/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java +++ b/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java @@ -5,13 +5,16 @@ import android.util.Log; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GoogleApiAvailability; import com.google.firebase.iid.FirebaseInstanceId; +import com.google.firebase.iid.InstanceIdResult; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.utils.PhoneHelper; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; +import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.stanzas.IqPacket; @@ -19,82 +22,148 @@ import rocks.xmpp.addr.Jid; public class PushManagementService { - protected final XmppConnectionService mXmppConnectionService; + protected final XmppConnectionService mXmppConnectionService; - PushManagementService(XmppConnectionService service) { - this.mXmppConnectionService = service; - } + PushManagementService(XmppConnectionService service) { + this.mXmppConnectionService = service; + } - void registerPushTokenOnServer(final Account account) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": has push support"); - retrieveFcmInstanceToken(token -> { - final String androidId = PhoneHelper.getAndroidId(mXmppConnectionService); - final Jid appServer = Jid.of(mXmppConnectionService.getString(R.string.app_server)); - IqPacket packet = mXmppConnectionService.getIqGenerator().pushTokenToAppServer(appServer, token, androidId); - mXmppConnectionService.sendIqPacket(account, packet, (a, p) -> { - Element command = p.findChild("command", "http://jabber.org/protocol/commands"); - if (p.getType() == IqPacket.TYPE.RESULT && command != null) { - Element x = command.findChild("x", Namespace.DATA); - if (x != null) { - Data data = Data.parse(x); - try { - String node = data.getValue("node"); - String secret = data.getValue("secret"); - Jid jid = Jid.of(data.getValue("jid")); - if (node != null && secret != null) { - enablePushOnServer(a, jid, node, secret); - } - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } - } - } else { - Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": invalid response from app server"); - } - }); - }); - } + private static Data findResponseData(IqPacket response) { + final Element command = response.findChild("command", Namespace.COMMANDS); + final Element x = command == null ? null : command.findChild("x", Namespace.DATA); + return x == null ? null : Data.parse(x); + } - private void enablePushOnServer(final Account account, final Jid jid, final String node, final String secret) { - IqPacket enable = mXmppConnectionService.getIqGenerator().enablePush(jid, node, secret); - mXmppConnectionService.sendIqPacket(account, enable, (a, p) -> { - if (p.getType() == IqPacket.TYPE.RESULT) { - Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": successfully enabled push on server"); - } else if (p.getType() == IqPacket.TYPE.ERROR) { - Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": enabling push on server failed"); - } - }); - } + private Jid getAppServer() { + return Jid.of(mXmppConnectionService.getString(R.string.app_server)); + } - private void retrieveFcmInstanceToken(final OnGcmInstanceTokenRetrieved instanceTokenRetrieved) { - new Thread(() -> { - try { - instanceTokenRetrieved.onGcmInstanceTokenRetrieved(FirebaseInstanceId.getInstance().getToken()); - } catch (Exception e) { - Log.d(Config.LOGTAG, "unable to get push token",e); - } - }).start(); + void registerPushTokenOnServer(final Account account) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": has push support"); + retrieveFcmInstanceToken(token -> { + final String androidId = PhoneHelper.getAndroidId(mXmppConnectionService); + final IqPacket packet = mXmppConnectionService.getIqGenerator().pushTokenToAppServer(getAppServer(), token, androidId); + mXmppConnectionService.sendIqPacket(account, packet, (a, response) -> { + final Data data = findResponseData(response); + if (response.getType() == IqPacket.TYPE.RESULT && data != null) { + try { + String node = data.getValue("node"); + String secret = data.getValue("secret"); + Jid jid = Jid.of(data.getValue("jid")); + if (node != null && secret != null) { + enablePushOnServer(a, jid, node, secret); + } + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } + } else { + Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": invalid response from app server"); + } + }); + }); + } - } + void registerPushTokenOnServer(final Conversation conversation) { + Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": room "+conversation.getJid().asBareJid()+" has push support"); + retrieveFcmInstanceToken(token -> { + final Jid muc = conversation.getJid().asBareJid(); + final String androidId = PhoneHelper.getAndroidId(mXmppConnectionService); + final IqPacket packet = mXmppConnectionService.getIqGenerator().pushTokenToAppServer(getAppServer(), token, androidId, muc); + packet.setTo(muc); + mXmppConnectionService.sendIqPacket(conversation.getAccount(), packet, (a, response) -> { + final Data data = findResponseData(response); + if (response.getType() == IqPacket.TYPE.RESULT && data != null) { + try { + final String node = data.getValue("node"); + final String secret = data.getValue("secret"); + final Jid jid = Jid.of(data.getValue("jid")); + if (node != null && secret != null) { + enablePushOnServer(conversation, jid, node, secret); + } + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } + } else { + Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": invalid response from app server"); + } + }); + }); + } + + private void enablePushOnServer(final Account account, final Jid appServer, final String node, final String secret) { + final IqPacket enable = mXmppConnectionService.getIqGenerator().enablePush(appServer, node, secret); + mXmppConnectionService.sendIqPacket(account, enable, (a, p) -> { + if (p.getType() == IqPacket.TYPE.RESULT) { + Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": successfully enabled push on server"); + } else if (p.getType() == IqPacket.TYPE.ERROR) { + Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": enabling push on server failed"); + } + }); + } + + private void enablePushOnServer(final Conversation conversation, final Jid appServer, final String node, final String secret) { + final Jid muc = conversation.getJid().asBareJid(); + final IqPacket enable = mXmppConnectionService.getIqGenerator().enablePush(appServer, node, secret); + enable.setTo(muc); + mXmppConnectionService.sendIqPacket(conversation.getAccount(), enable, (a, p) -> { + if (p.getType() == IqPacket.TYPE.RESULT) { + Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": successfully enabled push on " + muc); + if (conversation.setAttribute(Conversation.ATTRIBUTE_ALWAYS_NOTIFY, node)) { + mXmppConnectionService.updateConversation(conversation); + } + } else if (p.getType() == IqPacket.TYPE.ERROR) { + Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": enabling push on " + muc + " failed"); + } + }); + } + + public void disablePushOnServer(final Conversation conversation) { + final Jid muc = conversation.getJid().asBareJid(); + final String node = conversation.getAttribute(Conversation.ATTRIBUTE_PUSH_NODE); + if (node != null) { + final IqPacket disable = mXmppConnectionService.getIqGenerator().disablePush(getAppServer(), node); + disable.setTo(muc); + mXmppConnectionService.sendIqPacket(conversation.getAccount(), disable, (account, response) -> { + if (response.getType() == IqPacket.TYPE.ERROR) { + Log.d(Config.LOGTAG,account.getJid().asBareJid()+": unable to disable push for room "+muc); + } + }); + } else { + Log.d(Config.LOGTAG,conversation.getAccount().getJid().asBareJid()+": room "+muc+" has no stored node. unable to disable push"); + } + } + + private void retrieveFcmInstanceToken(final OnGcmInstanceTokenRetrieved instanceTokenRetrieved) { + FirebaseInstanceId.getInstance().getInstanceId().addOnCompleteListener(task -> { + if (!task.isSuccessful()) { + Log.d(Config.LOGTAG, "unable to get Firebase instance token", task.getException()); + } + final InstanceIdResult result = task.getResult(); + if (result != null) { + instanceTokenRetrieved.onGcmInstanceTokenRetrieved(result.getToken()); + } + }); + + } - public boolean available(Account account) { - final XmppConnection connection = account.getXmppConnection(); - return connection != null - && connection.getFeatures().sm() - && connection.getFeatures().push() - && playServicesAvailable(); - } + public boolean available(Account account) { + final XmppConnection connection = account.getXmppConnection(); + return connection != null + && connection.getFeatures().sm() + && connection.getFeatures().push() + && playServicesAvailable(); + } - private boolean playServicesAvailable() { - return GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(mXmppConnectionService) == ConnectionResult.SUCCESS; - } + private boolean playServicesAvailable() { + return GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(mXmppConnectionService) == ConnectionResult.SUCCESS; + } - public boolean isStub() { - return false; - } + public boolean isStub() { + return false; + } - interface OnGcmInstanceTokenRetrieved { - void onGcmInstanceTokenRetrieved(String token); - } + interface OnGcmInstanceTokenRetrieved { + void onGcmInstanceTokenRetrieved(String token); + } }