I use Influence to implement XMPP-based chats in our Android app. So far it works fine, tested with another phone with Conversations installed. The only caveat is Influence doesn't support OMEMO encryption yet, so have to disable it first. The good thing is Smack, which powers Influence, already support OMEMO, so I just need to add more code.
So this is my modified XMPPConnection.java (check doTrust(), sendOmemoMessage() and some OMEMO stuffs inside connect()):
public class XMPPConnection implements ConnectionListener {
private final static String LOG_TAG = "XMPPConnection";
private LoginCredentials credentials = new LoginCredentials();
private XMPPTCPConnection connection = null;
private NetworkHandler networkHandler;
private Context context;
private Roster roster;
private MamManager mamManager;
private OmemoManager omemoManager;
public enum ConnectionState {
CONNECTED,
DISCONNECTED
}
public enum SessionState {
LOGGED_IN,
LOGGED_OUT
}
public XMPPConnection(Context context) {
this.context = context;
String jid = Prefs.getString("jid", null);
String password = Prefs.getString("pass", null);
if(jid != null && password != null) {
String username = jid.split("@")[0];
String jabberHost = jid.split("@")[1];
credentials.username = username;
credentials.jabberHost = jabberHost;
credentials.password = password;
}
networkHandler = new NetworkHandler();
}
public void connect() throws XMPPException, IOException, SmackException, EmptyLoginCredentialsException, InterruptedException {
if(credentials.isEmpty()) {
throw new EmptyLoginCredentialsException();
}
if(connection == null) {
XMPPTCPConnectionConfiguration conf = XMPPTCPConnectionConfiguration.builder()
.setXmppDomain(credentials.jabberHost)
.setHost(credentials.jabberHost)
.setUsernameAndPassword(credentials.username, credentials.password)
.setResource(AppHelper.APP_NAME)
.setKeystoreType(null)
.setSecurityMode(ConnectionConfiguration.SecurityMode.required)
.setCompressionEnabled(true)
.setConnectTimeout(8000)
.build();
connection = new XMPPTCPConnection(conf);
connection.setReplyTimeout(10 * 1000);
connection.addConnectionListener(this);
omemoManager = OmemoManager.getInstanceFor(connection);
omemoManager.setTrustCallback(new OmemoTrustCallback() {
private final Map<OmemoFingerprint, TrustState> trustStateMap = new HashMap<>();
@Override
public TrustState getTrust(OmemoDevice device, OmemoFingerprint fingerprint) {
return trustStateMap.get(fingerprint) != null ? trustStateMap.get(fingerprint) : TrustState.undecided;
}
@Override
public void setTrust(OmemoDevice device, OmemoFingerprint fingerprint, TrustState state) {
trustStateMap.put(fingerprint, state);
}
});
omemoManager.addOmemoMessageListener(new OmemoMessageListener() {
@Override
public void onOmemoMessageReceived(Stanza s, OmemoMessage.Received m) {
System.out.println(m.getSenderDevice() + ": " + (m.getBody() != null ? m.getBody() : "<keyTransportMessage>"));
}
@Override
public void onOmemoCarbonCopyReceived(CarbonExtension.Direction d, Message cc, Message wm, OmemoMessage.Received m) {
onOmemoMessageReceived(cc, m);
}
});
omemoManager.addOmemoMucMessageListener(new OmemoMucMessageListener() {
@Override
public void onOmemoMucMessageReceived(MultiUserChat muc, Stanza s, OmemoMessage.Received m) {
System.out.println(s.getFrom() + ":" + m.getSenderDevice().getDeviceId() + ": " + (m.getBody() != null ? m.getBody() : "<keyTransportMessage>"));
}
});
}
if(credentials.jabberHost.equals("") && credentials.password.equals("") && credentials.username.equals("")){
throw new IOException();
}
try {
connection.connect();
connection.login(credentials.username, credentials.password);
omemoManager.initialize();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (NullPointerException e) {
throw new IOException();
} catch (CorruptedOmemoKeyException ce){
throw new RuntimeException();
}
ChatManager.getInstanceFor(connection).addIncomingListener(networkHandler);
ReconnectionManager reconnectionManager = ReconnectionManager.getInstanceFor(connection);
ReconnectionManager.setEnabledPerDefault(true);
reconnectionManager.enableAutomaticReconnection();
roster = roster.getInstanceFor(connection);
roster.setSubscriptionMode(Roster.SubscriptionMode.accept_all);
roster.addPresenceEventListener(networkHandler);
AppHelper.setJid(credentials.username + "@" + credentials.jabberHost);
mamManager = MamManager.getInstanceFor(connection);
try {
if(mamManager.isSupported()) {
MamManager.getInstanceFor(connection).enableMamForAllMessages();
} else {
mamManager = null;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
if(AppHelper.isIsMainActivityDestroyed()) {
sendUserPresence(new Presence(Presence.Type.unavailable));
}
}
public void disconnect() {
Prefs.putBoolean("logged_in", false);
if(connection != null) {
connection.disconnect();
connection = null;
}
}
@Override
public void connected(org.jivesoftware.smack.XMPPConnection connection) {
XMPPConnectionService.CONNECTION_STATE = ConnectionState.CONNECTED;
}
@Override
public void authenticated(org.jivesoftware.smack.XMPPConnection connection, boolean resumed) {
XMPPConnectionService.SESSION_STATE = SessionState.LOGGED_IN;
Prefs.putBoolean("logged_in", true);
EventBus.getDefault().post(new AuthenticationStatusEvent(AuthenticationStatusEvent.CONNECT_AND_LOGIN_SUCCESSFUL));
}
@Override
public void connectionClosed() {
XMPPConnectionService.CONNECTION_STATE = ConnectionState.DISCONNECTED;
XMPPConnectionService.SESSION_STATE = SessionState.LOGGED_OUT;
Prefs.putBoolean("logged_in", false);
}
@Override
public void connectionClosedOnError(Exception e) {
XMPPConnectionService.CONNECTION_STATE = ConnectionState.DISCONNECTED;
XMPPConnectionService.SESSION_STATE = SessionState.LOGGED_OUT;
Prefs.putBoolean("logged_in", false);
Log.e(LOG_TAG, "Connection closed, exception occurred");
e.printStackTrace();
}
public String sendMessage(EntityBareJid recipientJid, String messageText) {
doTrust(recipientJid);
Chat chat = ChatManager.getInstanceFor(connection).chatWith(recipientJid);
MessageBuilder messageBuilder = connection.getStanzaFactory().buildMessageStanza();
try {
Message message = new Message(recipientJid, Message.Type.chat);
message.setBody(messageText);
chat.send(message);
return message.getStanzaId();
} catch (SmackException.NotConnectedException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
public String sendOmemoMessage(EntityBareJid recipientJid, String messageText) {
Chat chat = ChatManager.getInstanceFor(connection).chatWith(recipientJid);
MessageBuilder messageBuilder = connection.getStanzaFactory().buildMessageStanza();
try {
Message omemoMessage = omemoManager.encrypt(recipientJid, messageText).buildMessage(messageBuilder, recipientJid);
omemoMessage.setBody(messageText);
connection.sendStanza(omemoMessage);
return omemoMessage.getStanzaId();
} catch (SmackException.NotConnectedException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (CryptoFailedException e) {
throw new RuntimeException(e);
} catch (UndecidedOmemoIdentityException e) {
throw new RuntimeException(e);
} catch (SmackException.NoResponseException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (SmackException.NotLoggedInException e) {
throw new RuntimeException(e);
}
return null;
}
public XMPPTCPConnection getConnection() {
return connection;
}
public byte[] getAvatar(EntityBareJid jid) {
if(isConnectionAlive()) {
VCardManager manager = VCardManager.getInstanceFor(connection);
byte[] avatar = null;
try {
avatar = manager.loadVCard(jid).getAvatar();
} catch (SmackException.NoResponseException e) {
e.printStackTrace();
} catch (XMPPException.XMPPErrorException e) {
e.printStackTrace();
} catch (SmackException.NotConnectedException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return avatar;
}
return null;
}
public Set<RosterEntry> getContactList() {
if(isConnectionAlive()) {
while (roster == null);
return roster.getEntries();
}
return null;
}
public boolean isConnectionAlive() {
if(XMPPConnectionService.CONNECTION_STATE.equals(ConnectionState.CONNECTED) && XMPPConnectionService.SESSION_STATE.equals(SessionState.LOGGED_IN)) {
return true;
} else {
return false;
}
}
public Presence getUserPresence(BareJid jid) {
return roster.getPresence(jid);
}
public void sendUserPresence(Presence presence) {
if(connection != null) {
if(isConnectionAlive()) {
try {
connection.sendStanza(presence);
} catch (SmackException.NotConnectedException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public MamManager getMamManager() {
if(isConnectionAlive()) {
return mamManager;
}
return null;
}
public void doTrust(BareJid contact){
HashMap<OmemoDevice, OmemoFingerprint> devices;
try {
devices = omemoManager.getActiveFingerprints(contact);
for (OmemoDevice d : devices.keySet()) {
omemoManager.trustOmemoIdentity(d, devices.get(d));
}
} catch (CorruptedOmemoKeyException | CannotEstablishOmemoSessionException | SmackException.NoResponseException e) {
Log.e("XMPPConnection", "Unexpected excetion: "+e.getMessage());
return;
} catch (SmackException.NotConnectedException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (SmackException.NotLoggedInException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
Then I ran influence. Adding other XMPP users works fine as usual. Error happens when I'm trying to sending a message. The app crashes. Found this on logcat:
FATAL EXCEPTION: Thread-5
Process: com.anta40.app.influencereloaded, PID: 15292
java.lang.IllegalStateException: No OmemoService registered
at org.jivesoftware.smackx.omemo.OmemoService.getInstance(OmemoService.java:127)
at org.jivesoftware.smackx.omemo.OmemoManager.<init>(OmemoManager.java:117)
at org.jivesoftware.smackx.omemo.OmemoManager.getInstanceFor(OmemoManager.java:190)
at com.anta40.app.influencereloaded.XMPPConnection.connect(XMPPConnection.java:115)
at com.anta40.app.influencereloaded.XMPPConnectionService.createConnection(XMPPConnectionService.java:73)
at com.anta40.app.influencereloaded.XMPPConnectionService.lambda$onServiceStart$0(XMPPConnectionService.java:46)
at com.anta40.app.influencereloaded.XMPPConnectionService.$r8$lambda$Vl6-8eZjycEqdLj8xB5HOO4Ycrw(Unknown Source:0)
at com.anta40.app.influencereloaded.XMPPConnectionService$$ExternalSyntheticLambda0.run(Unknown Source:2)
at java.lang.Thread.run(Thread.java:1012)
This line:
at com.anta40.app.influencereloaded.XMPPConnection.connect(XMPPConnection.java:115)
refers to
omemoManager = OmemoManager.getInstanceFor(connection);
What's wrong here? I already upgraded the Smack lib to v4.4.6, BTW.