package org.mercury_im.messenger.service; import android.app.Notification; import android.app.PendingIntent; import android.app.Service; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.util.Log; import android.util.LongSparseArray; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.chat2.Chat; import org.jivesoftware.smack.chat2.ChatManager; import org.jivesoftware.smack.chat2.IncomingChatMessageListener; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.roster.Roster; import org.jivesoftware.smack.roster.RosterEntry; import org.jivesoftware.smack.roster.RosterLoadedListener; import org.jivesoftware.smack.tcp.XMPPTCPConnection; import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration; import org.jivesoftware.smackx.ping.android.ServerPingWithAlarmManager; import org.jxmpp.jid.EntityBareJid; import org.mercury_im.messenger.MercuryImApplication; import org.mercury_im.messenger.Notifications; import org.mercury_im.messenger.R; import org.mercury_im.messenger.persistence.model.AccountModel; import org.mercury_im.messenger.persistence.repository.AccountRepository; import org.mercury_im.messenger.persistence.repository.ContactRepository; import org.mercury_im.messenger.persistence.room.AppDatabase; import org.mercury_im.messenger.persistence.room.model.RoomAccountModel; import org.mercury_im.messenger.persistence.room.model.RoomContactModel; import org.mercury_im.messenger.ui.MainActivity; import org.mercury_im.messenger.xmpp_android.ParcelableXMPPTCPConnectionConfiguration; import java.io.IOException; import java.util.List; import java.util.Set; import javax.inject.Inject; /** * Started, Bound Service, which is responsible for managing {@link XMPPConnection XMPPConnections} * affiliated with registered accounts. */ public class XmppConnectionService extends Service { private static final String TAG = MercuryImApplication.TAG; private static final String APP = "org.olomono.mercury"; private static final String SERVICE = APP + ".XmppConnectionService"; private static final String ACTION = SERVICE + ".ACTION"; private static final String EVENT = SERVICE + ".EVENT"; private static final String EXTRA = SERVICE + ".EXTRA"; private static final String STATUS = SERVICE + ".STATUS"; // ACTIONS public static final String ACTION_START = ACTION + ".START"; public static final String ACTION_STOP = ACTION + ".STOP"; public static final String ACTION_CONNECT = ACTION + ".CONNECT"; public static final String ACTION_DISCONNECT = ACTION + ".DISCONNECT"; public static final String ACTION_PING = ACTION + ".PING"; // EVENTS public static final String EVENT_INCOMING_MESSAGE = EVENT + ".INCOMING_MESSAGE"; public static final String EVENT_OUTGOING_MESSAGE = EVENT + ".OUTGOING_MESSAGE"; // EXTRAS public static final String EXTRA_CONFIGURATION = EXTRA + ".CONFIGURATION"; public static final String EXTRA_ACCOUNT_ID = EXTRA + ".ACCOUNT_ID"; // STATUSES public static final String STATUS_SUCCESS = STATUS + ".SUCCESS"; public static final String STATUS_FAILURE = STATUS + ".FAILURE"; @Inject ContactRepository rosterRepository; @Inject AccountRepository accountRepository; private final LongSparseArray connections = new LongSparseArray<>(); private Handler uiHandler; @NonNull @Override public final IBinder onBind(Intent intent) { return new Binder(this); } @Override public void onCreate() { super.onCreate(); Log.d(TAG, "onCreate()"); MercuryImApplication.getApplication().getAppComponent().inject(this); // Begin life cycle of Ping Manager. // The Manager will automatically detect newly created connections and ping the server // every half hour if necessary. ServerPingWithAlarmManager.onCreate(this); } @Override public void onDestroy() { super.onDestroy(); Log.d(TAG, "onDestroy()"); // End life cycle of Ping Manager. ServerPingWithAlarmManager.onDestroy(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (uiHandler == null) { uiHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(android.os.Message msg) { switch (msg.what) { case 1: Message m = (Message) msg.obj; Toast.makeText(XmppConnectionService.this, (m.getFrom() != null ? m.getFrom().toString() : "null") + ": " + m.getBody(), Toast.LENGTH_LONG).show(); } } }; } Log.d(TAG, "onStartCommand(" + intent + ")"); if (intent == null) { startAndDisplayForegroundNotification(); } else { String action = intent.getAction(); action = action == null ? "" : action; switch (action) { case ACTION_START: startAndDisplayForegroundNotification(); startConnections(); break; case ACTION_STOP: stopForeground(true); break; case ACTION_CONNECT: ParcelableXMPPTCPConnectionConfiguration configuration = intent.getParcelableExtra(EXTRA_CONFIGURATION); if (configuration == null) { Log.e(TAG, "Configuration is null."); return START_STICKY_COMPATIBILITY; } long accountId = intent.getLongExtra(EXTRA_ACCOUNT_ID, -1); if (accountId == -1) { Log.d(TAG, "No AccountID provided."); return START_STICKY_COMPATIBILITY; } new Thread() { public void run() { XMPPConnection connection = startConnection(configuration, accountId); connections.put(accountId, connection); } }.start(); break; default: break; } } return START_STICKY_COMPATIBILITY; } public void startAndDisplayForegroundNotification() { Log.d(TAG, "startAndDisplayForegroundNotification()"); Notification notification = getForegroundNotification(getApplicationContext(), connections.size()); startForeground(Notifications.FOREGROUND_SERVICE_ID, notification); } static Notification getForegroundNotification(Context context, int numConnections) { Intent startMainActivityIntent = new Intent(context, MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, startMainActivityIntent, 0); return new NotificationCompat.Builder(context, Notifications.NOTIFICATION_CHANNEL__FOREGROUND_SERVICE) .setContentTitle("Mercury") .setContentText(numConnections + " connections.") .setSmallIcon(R.drawable.ic_send_black_24dp) .setContentIntent(pendingIntent) .build(); } public void startConnections() { new Thread() { @Override public void run() { synchronized (connections) { List accounts = accountRepository.getAllAccounts(); if (accounts == null) return; for (AccountModel a : accounts) { if (connections.get(a.getId()) != null) { continue; } // XMPPConnection connection = startConnection(a.getJid(), a.getPassword(), a.getId()); // connections.put(a.getId(), connection); } } } }.start(); } private XMPPConnection startConnection(ParcelableXMPPTCPConnectionConfiguration connectionConfiguration, long accountId) { XMPPTCPConnection con = null; try { XMPPTCPConnectionConfiguration conf = connectionConfiguration.getConfiguration(); con = new XMPPTCPConnection(conf); con.connect().login(); } catch ( XMPPException e) { e.printStackTrace(); } catch ( SmackException e) { e.printStackTrace(); } catch ( IOException e) { e.printStackTrace(); } catch ( InterruptedException e) { e.printStackTrace(); } NotificationManagerCompat.from(getApplicationContext()).notify(Notifications.FOREGROUND_SERVICE_ID, getForegroundNotification(getApplicationContext(), connections.size())); Roster roster = Roster.getInstanceFor(con); roster.addRosterLoadedListener(new RosterLoadedListener() { @Override public void onRosterLoaded(Roster roster) { Set entries = roster.getEntries(); for (RosterEntry e : entries) { Log.d(TAG, "Inserting Roster entry " + e.getJid().toString()); RoomContactModel contact = new RoomContactModel(); contact.setAccountId(accountId); // TODO: Fix } } @Override public void onRosterLoadingFailed(Exception exception) { Log.d(TAG, "Roster Loading failed", exception); } }); try { roster.reload(); } catch (SmackException.NotLoggedInException e) { e.printStackTrace(); } catch (SmackException.NotConnectedException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } ChatManager chatManager = ChatManager.getInstanceFor(con); chatManager.addIncomingListener(new IncomingChatMessageListener() { @Override public void newIncomingMessage(EntityBareJid from, Message message, Chat chat) { android.os.Message msg = uiHandler.obtainMessage(1, message); msg.sendToTarget(); } }); return con; } public XMPPConnection getConnection(long accountId) { return connections.get(accountId); } public void putConnection(int accountId, XMPPConnection connection) { connections.put(accountId, connection); } public class Binder extends android.os.Binder { private final XmppConnectionService service; public Binder(XmppConnectionService service) { this.service = service; } public XmppConnectionService getService() { return service; } } }