Initial commit

This commit is contained in:
Paul Schaub 2020-02-08 17:56:24 +01:00
parent 9d626bf787
commit 0d90e6535e
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
16 changed files with 551 additions and 0 deletions

View File

@ -29,6 +29,8 @@ include 'smack-core',
'smack-omemo-signal-integration-test',
'smack-repl',
'smack-openpgp',
'smack-messenger',
'smack-messenger-android',
'smack-xmlparser',
'smack-xmlparser-stax',
'smack-xmlparser-xpp3'

View File

@ -0,0 +1,7 @@
dependencies {
implementation project(":smack-messenger")
implementation project(":smack-android-extensions")
// Add the Android jar to the Eclipse .classpath.
compileOnly files(androidBootClasspath)
}

View File

@ -0,0 +1,71 @@
package org.jivesoftware.smacks.messenger.android;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.jivesoftware.smacks.messenger.android.csi.util.AbstractActivityLifecycleCallbacks;
import org.jivesoftware.smackx.messenger.csi.ClientStateListener;
import android.app.Activity;
import android.app.Application;
/**
* Android Utility class that observes the current state of the application.
* If the application currently displays at least one active Activity, then registered
* {@link ClientStateListener ClientStateListeners} will be notified via {@link ClientStateListener#onClientInForeground()}.
* If the application goes into the background, {@link ClientStateListener#onClientInBackground()} will be fired.
*
* Setup: During application startup, call {@link android.app.Application#registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks)}
* and pass an instance of {@link AndroidCsiManager} as argument.
*
* Remember to also register a {@link ClientStateListener} implementation (eg. the Messenger class from smack-messenger).
*/
public final class AndroidCsiManager extends AbstractActivityLifecycleCallbacks {
private static AndroidCsiManager INSTANCE;
private AtomicInteger activityReferences = new AtomicInteger(0);
private AtomicBoolean isActivityChangingConfiguration = new AtomicBoolean(false);
private final List<ClientStateListener> listeners = new ArrayList<>();
private AndroidCsiManager() {
}
public AndroidCsiManager getInstance() {
if (INSTANCE == null) {
INSTANCE = new AndroidCsiManager();
}
return INSTANCE;
}
@Override
public void onActivityStarted(Activity activity) {
if (activityReferences.incrementAndGet() == 1 && !isActivityChangingConfiguration.get()) {
for (ClientStateListener listener : listeners) {
listener.onClientInForeground();
}
}
}
@Override
public void onActivityStopped(Activity activity) {
isActivityChangingConfiguration.set(activity.isChangingConfigurations());
if (activityReferences.decrementAndGet() == 0 && !isActivityChangingConfiguration.get()) {
for (ClientStateListener listener : listeners) {
listener.onClientInBackground();
}
}
}
public void addClientStateListener(ClientStateListener listener) {
this.listeners.add(listener);
}
public void removeClientStateListener(ClientStateListener listener) {
this.listeners.remove(listener);
}
}

View File

@ -0,0 +1,45 @@
package org.jivesoftware.smacks.messenger.android.csi.util;
import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
/**
* Abstract class providing empty implementations of {@link Application.ActivityLifecycleCallbacks}.
*/
public abstract class AbstractActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks {
@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
}

View File

@ -0,0 +1,7 @@
dependencies {
implementation project(":smack-core")
implementation project(":smack-tcp")
implementation project(":smack-im")
implementation project(":smack-extensions")
implementation project(":smack-experimental")
}

View File

@ -0,0 +1,34 @@
package org.jivesoftware.smackx.messenger;
import java.util.UUID;
public class AccountRecord {
private final UUID accountId;
private final String username;
private final String password;
private final String serviceName;
public AccountRecord(UUID accountId, String username, String password, String serviceName) {
this.accountId = accountId;
this.username = username;
this.password = password;
this.serviceName = serviceName;
}
public UUID getAccountId() {
return accountId;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public String getServiceName() {
return serviceName;
}
}

View File

@ -0,0 +1,100 @@
package org.jivesoftware.smackx.messenger;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.jivesoftware.smack.ReconnectionManager;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.roster.Roster;
import org.jivesoftware.smackx.caps.EntityCapsManager;
import org.jivesoftware.smackx.carbons.CarbonManager;
import org.jivesoftware.smackx.csi.ClientStateIndicationManager;
import org.jivesoftware.smackx.iqversion.VersionManager;
import org.jivesoftware.smackx.messenger.connection.ConnectionFactory;
import org.jivesoftware.smackx.messenger.connection.XmppTcpConnectionFactory;
import org.jivesoftware.smackx.messenger.csi.ClientStateListener;
import org.jivesoftware.smackx.messenger.store.MessengerStore;
import org.jivesoftware.smackx.messenger.store.roster.RosterStoreAdapter;
import org.jivesoftware.smackx.sid.StableUniqueStanzaIdManager;
import org.jxmpp.stringprep.XmppStringprepException;
public class Messenger implements ClientStateListener {
private final Map<UUID, XmppAccount> accounts = new ConcurrentHashMap<>();
private final MessengerStore messengerStore;
private ConnectionFactory connectionFactory = new XmppTcpConnectionFactory();
public Messenger(MessengerStore store) {
this.messengerStore = store;
EntityCapsManager.setPersistentCache(store);
setGlobalDefaults();
}
private void setGlobalDefaults() {
ReconnectionManager.setEnabledPerDefault(true);
StableUniqueStanzaIdManager.setEnabledByDefault(true);
VersionManager.setAutoAppendSmackVersion(false);
}
public XmppAccount addAccount(UUID accountId, String username, String password, String serviceName)
throws XmppStringprepException {
XMPPConnection connection = connectionFactory.createConnection(username, password, serviceName);
XmppAccount xmppAccount = new XmppAccount(accountId, connection);
accounts.put(accountId, xmppAccount);
offlineAccountSetup(xmppAccount);
return xmppAccount;
}
private void offlineAccountSetup(XmppAccount account) {
Roster.getInstanceFor(account.getConnection()).setRosterStore(
new RosterStoreAdapter(account.getAccountId(), messengerStore));
}
private void onlineAccountSetup(XmppAccount account)
throws InterruptedException, XMPPException, SmackException {
if (CarbonManager.getInstanceFor(account.getConnection()).isSupportedByServer()) {
CarbonManager.getInstanceFor(account.getConnection()).enableCarbons();
}
}
@Override
public synchronized void onClientInForeground() {
for (XmppAccount connection : accounts.values()) {
trySetCsiActive(connection);
}
}
private void trySetCsiActive(XmppAccount connection) {
try {
ClientStateIndicationManager.active(connection.getConnection());
} catch (SmackException.NotConnectedException | InterruptedException e) {
e.printStackTrace();
}
}
@Override
public synchronized void onClientInBackground() {
for (XmppAccount connection : accounts.values()) {
trySetCsiInactive(connection);
}
}
private void trySetCsiInactive(XmppAccount connection) {
try {
ClientStateIndicationManager.inactive(connection.getConnection());
} catch (SmackException.NotConnectedException | InterruptedException e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,36 @@
package org.jivesoftware.smackx.messenger;
import java.io.IOException;
import java.util.UUID;
import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
public class XmppAccount {
private final UUID accountId;
private final XMPPConnection connection;
public XmppAccount(UUID accountId, XMPPConnection connection) {
this.connection = connection;
this.accountId = accountId;
}
public XMPPConnection getConnection() {
return connection;
}
public UUID getAccountId() {
return accountId;
}
public void login() throws InterruptedException, XMPPException, SmackException, IOException {
((AbstractXMPPConnection) getConnection()).connect().login();
}
public boolean isLoggedIn() {
return getConnection().isAuthenticated();
}
}

View File

@ -0,0 +1,10 @@
package org.jivesoftware.smackx.messenger.connection;
import org.jivesoftware.smack.XMPPConnection;
import org.jxmpp.stringprep.XmppStringprepException;
public interface ConnectionFactory {
XMPPConnection createConnection(String username, String password, String serviceName) throws XmppStringprepException;
}

View File

@ -0,0 +1,22 @@
package org.jivesoftware.smackx.messenger.connection;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jxmpp.stringprep.XmppStringprepException;
public class XmppTcpConnectionFactory implements ConnectionFactory {
@Override
public XMPPConnection createConnection(String username, String password, String serviceName) throws XmppStringprepException {
XMPPTCPConnectionConfiguration configuration = XMPPTCPConnectionConfiguration.builder()
.setConnectTimeout(60 * 1000)
.setHost(serviceName)
.setUsernameAndPassword(username, password)
.build();
XMPPTCPConnection connection = new XMPPTCPConnection(configuration);
return connection;
}
}

View File

@ -0,0 +1,7 @@
package org.jivesoftware.smackx.messenger.csi;
public interface ClientStateListener {
void onClientInForeground();
void onClientInBackground();
}

View File

@ -0,0 +1,105 @@
package org.jivesoftware.smackx.messenger.store;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.jivesoftware.smack.roster.packet.RosterPacket;
import org.jivesoftware.smack.roster.rosterstore.DirectoryRosterStore;
import org.jivesoftware.smackx.caps.cache.SimpleDirectoryPersistentCache;
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
import org.jivesoftware.smackx.messenger.AccountRecord;
import org.jxmpp.jid.Jid;
public class FilebasedMessengerStore implements MessengerStore {
private final File storeBaseDir;
private final Map<UUID, DirectoryRosterStore> rosterStoreMap = new ConcurrentHashMap<>();
private final SimpleDirectoryPersistentCache entityCapsCache;
private final Map<UUID, AccountRecord> accounts = new ConcurrentHashMap<>();
public FilebasedMessengerStore(File storeBaseDir) {
this.storeBaseDir = storeBaseDir;
entityCapsCache = new SimpleDirectoryPersistentCache(new File(storeBaseDir, "entityCaps"));
}
private DirectoryRosterStore getRosterStore(UUID accountId) {
DirectoryRosterStore store = rosterStoreMap.get(accountId);
if (store == null) {
File accountDir = new File(storeBaseDir, accountId.toString());
File rosterDir = new File(accountDir, "roster");
store = DirectoryRosterStore.open(rosterDir);
if (store == null) {
store = DirectoryRosterStore.init(rosterDir);
}
rosterStoreMap.put(accountId, store);
}
return store;
}
@Override
public void addDiscoverInfoByNodePersistent(String nodeVer, DiscoverInfo info) {
entityCapsCache.addDiscoverInfoByNodePersistent(nodeVer, info);
}
@Override
public DiscoverInfo lookup(String nodeVer) {
return entityCapsCache.lookup(nodeVer);
}
@Override
public void emptyCache() {
entityCapsCache.emptyCache();
}
@Override
public List<AccountRecord> getAllAccounts() {
return new ArrayList<>(accounts.values());
}
@Override
public AccountRecord getAccount(UUID accountId) {
return accounts.get(accountId);
}
@Override
public List<RosterPacket.Item> getEntries(UUID accountId) {
return getRosterStore(accountId).getEntries();
}
@Override
public RosterPacket.Item getEntry(UUID accountId, Jid bareJid) {
return getRosterStore(accountId).getEntry(bareJid);
}
@Override
public String getRosterVersion(UUID accountId) {
return getRosterStore(accountId).getRosterVersion();
}
@Override
public boolean addEntry(UUID accountId, RosterPacket.Item item, String version) {
return getRosterStore(accountId).addEntry(item, version);
}
@Override
public boolean resetEntries(UUID accountId, Collection<RosterPacket.Item> items, String version) {
return getRosterStore(accountId).resetEntries(items, version);
}
@Override
public boolean removeEntry(UUID accountId, Jid bareJid, String version) {
return getRosterStore(accountId).removeEntry(bareJid, version);
}
@Override
public void resetStore(UUID accountId) {
getRosterStore(accountId).resetStore();
}
}

View File

@ -0,0 +1,9 @@
package org.jivesoftware.smackx.messenger.store;
import org.jivesoftware.smackx.caps.cache.EntityCapsPersistentCache;
import org.jivesoftware.smackx.messenger.store.account.AccountStore;
import org.jivesoftware.smackx.messenger.store.roster.GlobalRosterStore;
public interface MessengerStore extends EntityCapsPersistentCache, AccountStore, GlobalRosterStore {
}

View File

@ -0,0 +1,13 @@
package org.jivesoftware.smackx.messenger.store.account;
import java.util.List;
import java.util.UUID;
import org.jivesoftware.smackx.messenger.AccountRecord;
public interface AccountStore {
List<AccountRecord> getAllAccounts();
AccountRecord getAccount(UUID accountId);
}

View File

@ -0,0 +1,26 @@
package org.jivesoftware.smackx.messenger.store.roster;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import org.jivesoftware.smack.roster.packet.RosterPacket;
import org.jxmpp.jid.Jid;
public interface GlobalRosterStore {
List<RosterPacket.Item> getEntries(UUID accountId);
RosterPacket.Item getEntry(UUID accountId, Jid bareJid);
String getRosterVersion(UUID accountId);
boolean addEntry(UUID accountId, RosterPacket.Item item, String version);
boolean resetEntries(UUID accountId, Collection<RosterPacket.Item> items, String version);
boolean removeEntry(UUID accountId, Jid bareJid, String version);
void resetStore(UUID accountId);
}

View File

@ -0,0 +1,57 @@
package org.jivesoftware.smackx.messenger.store.roster;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import org.jivesoftware.smack.roster.packet.RosterPacket;
import org.jivesoftware.smack.roster.rosterstore.RosterStore;
import org.jivesoftware.smackx.messenger.store.roster.GlobalRosterStore;
import org.jxmpp.jid.Jid;
public class RosterStoreAdapter implements RosterStore {
private final GlobalRosterStore store;
private final UUID accountId;
public RosterStoreAdapter(UUID accountId, GlobalRosterStore globalRosterStore) {
this.store = globalRosterStore;
this.accountId = accountId;
}
@Override
public List<RosterPacket.Item> getEntries() {
return store.getEntries(accountId);
}
@Override
public RosterPacket.Item getEntry(Jid bareJid) {
return store.getEntry(accountId, bareJid);
}
@Override
public String getRosterVersion() {
return store.getRosterVersion(accountId);
}
@Override
public boolean addEntry(RosterPacket.Item item, String version) {
return store.addEntry(accountId, item, version);
}
@Override
public boolean resetEntries(Collection<RosterPacket.Item> items, String version) {
return store.resetEntries(accountId, items, version);
}
@Override
public boolean removeEntry(Jid bareJid, String version) {
return store.removeEntry(accountId, bareJid, version);
}
@Override
public void resetStore() {
store.resetStore(accountId);
}
}