package org.sechat.app;
/**
* Copyright (c) 2014 Sechat GbR <support@sechat.org>
*
* You should have received a copy of the MIT License
* along with this program (license.txt).
* If not, see <http://sechat.github.io/license.txt>.
*/
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.RosterEntry;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.provider.PrivacyProvider;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smackx.GroupChatInvitation;
import org.jivesoftware.smackx.PrivateDataManager;
import org.jivesoftware.smackx.bytestreams.ibb.provider.CloseIQProvider;
import org.jivesoftware.smackx.bytestreams.ibb.provider.DataPacketProvider;
import org.jivesoftware.smackx.bytestreams.socks5.provider.BytestreamsProvider;
import org.jivesoftware.smackx.packet.ChatStateExtension;
import org.jivesoftware.smackx.packet.LastActivity;
import org.jivesoftware.smackx.packet.OfflineMessageInfo;
import org.jivesoftware.smackx.packet.OfflineMessageRequest;
import org.jivesoftware.smackx.packet.SharedGroupsInfo;
import org.jivesoftware.smackx.packet.VCard;
import org.jivesoftware.smackx.provider.AdHocCommandDataProvider;
import org.jivesoftware.smackx.provider.DataFormProvider;
import org.jivesoftware.smackx.provider.DelayInformationProvider;
import org.jivesoftware.smackx.provider.DiscoverInfoProvider;
import org.jivesoftware.smackx.provider.DiscoverItemsProvider;
import org.jivesoftware.smackx.provider.MUCAdminProvider;
import org.jivesoftware.smackx.provider.MUCOwnerProvider;
import org.jivesoftware.smackx.provider.MUCUserProvider;
import org.jivesoftware.smackx.provider.MessageEventProvider;
import org.jivesoftware.smackx.provider.MultipleAddressesProvider;
import org.jivesoftware.smackx.provider.RosterExchangeProvider;
import org.jivesoftware.smackx.provider.StreamInitiationProvider;
import org.jivesoftware.smackx.provider.VCardProvider;
import org.jivesoftware.smackx.provider.XHTMLExtensionProvider;
import org.jivesoftware.smackx.search.UserSearch;
import org.sechat.app.activity.MessageBoard;
import org.sechat.app.activity.UserList;
import org.sechat.app.adapter.DataBaseAdapter;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Color;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.IBinder;
import android.os.Messenger;
import android.util.Base64;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
public class ThreadHelper {
String TAG = this.getClass().getName();
public static XMPPConnection xmppConnection = null;
public static String ACCOUNT_NAME = null;
public static String ACCOUNT_PASSWORD = null;
public final boolean D = true;
public final String appName = "3nc0App";
public final String HOST = "connect.3nc0.de";
public final String IP = "188.40.178.248";
public final int PORT = 5222;
private static String activeChatUser = null;
private static String nickName = null;
private static boolean activityVisible;
private static HashMap<String, Boolean> newMessages = new HashMap<String, Boolean>();
/**
* Database params
*/
public final static int DATABASE_VERSION = 17;
public final static String DATABASE = "3ncoApp";
// table
public final static String DB_USER_TABLE = "userTable";
public final static String DB_HISTORY_TABLE = "userHistory";
// rows
public final static String DB_ID = "id";
public final static String DB_NAME = "name";
public final static String DB_ME = "me";
public final static String DB_MESSAGE = "message";
public final static String DB_DATE = "date";
public final static String DB_PASSWORD = "password";
public final static String DB_PRIVATE = "private";
public final static String DB_PUBLIC = "public";
/**
* Service
*/
public static final int REPEAT_TIME = 1000 * 360;
public static final int USERLIST_REPEAT_TIME = 1000 * 10;
public static Thread listenerThread = null;
/////////////////////////////////////////////////////
// Starting some public function
/////////////////////////////////////////////////////
/**
* XMPP
* @throws XMPPException
*/
public void xmppConnect() throws XMPPException {
if (!(ThreadHelper.xmppConnection == null) &&
ThreadHelper.xmppConnection.isConnected()) return;
// Set configuration parameter
Roster.setDefaultSubscriptionMode(Roster.SubscriptionMode.manual);
ConnectionConfiguration config = new ConnectionConfiguration(HOST, PORT);
config.setSASLAuthenticationEnabled(false);
configure(ProviderManager.getInstance());
ThreadHelper.xmppConnection = new XMPPConnection(config);
ThreadHelper.xmppConnection.connect();
}
public boolean xmppLogin(Context context) {
if (ThreadHelper.xmppConnection == null)
return false;
if (ThreadHelper.xmppConnection.isAuthenticated())
return true;
DataBaseAdapter db = new DataBaseAdapter(context);
ThreadHelper.ACCOUNT_NAME = db.getName();
ThreadHelper.ACCOUNT_PASSWORD = db.getPassword();
db.close();
try {
ThreadHelper.xmppConnection.login(
ThreadHelper.ACCOUNT_NAME, ThreadHelper.ACCOUNT_PASSWORD);
} catch (XMPPException e) {
if (D) Log.d(TAG, e.getMessage(), e);
return false;
}
if (ThreadHelper.xmppConnection.isAuthenticated())
return true;
return false;
}
public boolean xmppConnectAndLogin(Context context) {
try {
xmppConnect();
} catch (XMPPException e) {
if (D) Log.d(TAG, e.getMessage(), e);
return false;
}
if (xmppLogin(context))
return true;
return false;
}
/**
* Send notification to status bar
*
* @param Context
* @param NotificationManager
* @param Notification
* @param CharSequence Title
* @param CharSequence Text
* @return void
*/
public void sendNotification(Context context, NotificationManager mNotificationManager,
Notification notifyDetails, CharSequence contentTitle, CharSequence contentText) {
// notify only if the app is in background
if (!ThreadHelper.isActivityVisible()) {
Intent notify = new Intent(context, MessageBoard.class);
notify.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
notify.putExtra("activeChatUser", contentTitle);
//notify.setAction(Intent.ACTION_MAIN);
//notify.addCategory(Intent.CATEGORY_LAUNCHER);
PendingIntent intent = PendingIntent.getActivity(context, 0,
notify, android.content.Intent.FLAG_ACTIVITY_NEW_TASK);
notifyDetails.setLatestEventInfo(context, contentTitle, contentText, intent);
notifyDetails.flags |= Notification.FLAG_AUTO_CANCEL;
// vibrate on new notification
notifyDetails.defaults |= Notification.DEFAULT_VIBRATE;
notifyDetails.vibrate = new long[]{100, 200, 100, 500};
// and turn on the status LED
notifyDetails.flags |= Notification.FLAG_SHOW_LIGHTS;
notifyDetails.ledARGB = Color.GREEN;
notifyDetails.ledOffMS = 300;
notifyDetails.ledOnMS = 300;
mNotificationManager.notify(0, notifyDetails);
}
}
/**
* Add a new chat message to the active chat
*/
public void addDiscussionEntry(Activity act, String user, String message, Boolean me) {
if (D) Log.e(TAG, "Add new discussion entry to "+user);
DataBaseAdapter db = new DataBaseAdapter(act.getBaseContext());
db.addMessage(user, message, me);
db.close();
// update/refresh the message board
if (getActiveChatUser() == null) {
hasUserNewMessages(user, true);
}
updateDiscussion(act);
}
public void updateDiscussion(Activity act) {
if (getActiveChatUser() == null) return;
DataBaseAdapter db = new DataBaseAdapter(act.getBaseContext());
final LinkedList<Discussion> discussions = db.getMessagesFrom(getActiveChatUser());
db.close();
act.runOnUiThread(new Runnable(){
@Override
public void run() {
MessageBoard.listItems.clear();
MessageBoard.listItems.addAll(discussions);
MessageBoard.msgAdapter.notifyDataSetChanged();
}
});
}
public boolean hasUserNewMessages(String jid) {
if (newMessages.containsKey(jid))
return newMessages.get(jid);
return false;
}
public void hasUserNewMessages(String jid, Boolean result) {
newMessages.put(jid, result);
}
public static boolean isActivityVisible() {
return activityVisible;
}
public static void activityResumed() {
activityVisible = true;
}
public static void activityPaused() {
activityVisible = false;
}
public void setActiveChatUser(String input) {
ThreadHelper.activeChatUser = input;
}
public String getActiveChatUser() {
return ThreadHelper.activeChatUser;
}
public void setNickName(String input) {
ThreadHelper.nickName = input;
}
public String getNickName() {
String nick = ThreadHelper.nickName;
if (nick == null)
return "";
return nick;
}
public String base64Encode(byte[] input) {
//encoding byte array into base 64
return Base64.encodeToString(input, Base64.DEFAULT).replaceAll("\\n", "");
}
public byte[] base64Decode(String input) {
//decoding byte array into base64
return Base64.decode(input, Base64.DEFAULT);
}
public void sendNotification(Context context, String message) {
sendNotification(context, message, Toast.LENGTH_SHORT);
}
public void sendNotification(Context context, String message, int length) {
Toast.makeText(context, message, length).show();
}
public String getMd5Sum(String in) {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.reset();
md5.update(in.getBytes());
byte[] result = md5.digest();
StringBuffer hexString = new StringBuffer();
for (int i=0; i<result.length; i++) {
if(result[i] <= 15 && result[i] >= 0){
hexString.append("0");
}
hexString.append(Integer.toHexString(0xFF & result[i]));
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
if (D) Log.e(TAG, e.getMessage());
}
return null;
}
public boolean isOnline(Context context) {
ConnectivityManager cm =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo netInfo = cm.getActiveNetworkInfo();
if (netInfo != null && netInfo.isConnected()) {
return true;
}
return false;
}
public void updateUserList(Activity act, User user) {
if (!ThreadHelper.xmppConnection.isAuthenticated()) {
if (D) Log.d(TAG, "XMPP connection is not authenticated!");
return;
}
if (UserList.listItems.contains(user)) {
if (D) Log.d(TAG, "Update userlist failed! User already in list.");
return;
}
ArrayList<User> list = new ArrayList<User>();;
if (!UserList.listItems.isEmpty())
list.addAll(UserList.listItems);
list.add(user);
updateUserList(act, list);
}
public void updateUserList(Activity act) {
if (!ThreadHelper.xmppConnection.isAuthenticated()) {
if (D) Log.d(TAG, "XMPP connection is not authenticated!");
return;
}
ArrayList<User> list = new ArrayList<User>();
Roster roster = ThreadHelper.xmppConnection.getRoster();
Iterator<RosterEntry> cit = roster.getEntries().iterator();
while(cit.hasNext()) {
RosterEntry entry = cit.next();
VCard vCard = new VCard();
try {
vCard.load(ThreadHelper.xmppConnection, entry.getUser());
} catch (XMPPException e) {
Log.e(TAG, e.getMessage(), e);
}
list.add(new User(entry.getUser(), vCard.getNickName(),
roster.getPresence(entry.getUser()).isAvailable()));
}
updateUserList(act, list);
}
private void updateUserList(Activity act, final ArrayList<User> list) {
act.runOnUiThread(new Runnable() {
@Override
public void run() {
// Update list and trigger notify on user adapter
UserList.listItems.clear();
UserList.listItems.addAll(list);
UserList.adapter.notifyDataSetChanged();
Integer setTo;
if (D) Log.d(TAG, "User-list notify data set changed!");
if (UserList.listItems.size() > 0)
setTo = View.GONE;
else setTo = View.VISIBLE;
// Display hint if the user has no contacts
UserList.contactInfoBox.setVisibility(setTo);
UserList.addAuto.setVisibility(setTo);
UserList.addManual.setVisibility(setTo);
// Remove update spinner
UserList.updateUserListBar.setVisibility(View.GONE);
}
});
}
public ServiceConnection conn = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder binder) {
new Messenger(binder);
}
public void onServiceDisconnected(ComponentName className) {}
};
/** ******************************************** **/
public void close(ObjectOutputStream oout) {
try {
if (D) Log.e(TAG, "Close socket!");
oout.close();
} catch (IOException e) {
if (D) Log.e(TAG, e.getMessage());
}
}
public void close(ObjectInputStream oin) {
try {
if (D) Log.e(TAG, "Close socket!");
oin.close();
} catch (IOException e) {
if (D) Log.e(TAG, e.getMessage());
}
}
/**
* It seems it's a known issue: the smack.providers file,
* usually in /META-INF folder in normal versions of smack,
* can't be loaded in Android because its jar packaging.
* So all the providers must be initialized by hand,
* as shown in Mike Ryan's answer in this thread:
* http://community.igniterealtime.org/message/201866#201866
*/
public void configure(ProviderManager pm) {
// private data storage
pm.addIQProvider("query","jabber:iq:private",
new PrivateDataManager.PrivateDataIQProvider());
// time
try {
pm.addIQProvider("query","jabber:iq:time",
Class.forName("org.jivesoftware.smackx.packet.Time"));
} catch (ClassNotFoundException e) {
Log.w(TAG, "Can't load class for org.jivesoftware.smackx.packet.Time");
}
// roster exchange
pm.addExtensionProvider("x","jabber:x:roster", new RosterExchangeProvider());
// message events
pm.addExtensionProvider("x","jabber:x:event", new MessageEventProvider());
// chat state
pm.addExtensionProvider("active","http://jabber.org/protocol/chatstates",
new ChatStateExtension.Provider());
pm.addExtensionProvider("composing","http://jabber.org/protocol/chatstates",
new ChatStateExtension.Provider());
pm.addExtensionProvider("paused","http://jabber.org/protocol/chatstates",
new ChatStateExtension.Provider());
pm.addExtensionProvider("inactive","http://jabber.org/protocol/chatstates",
new ChatStateExtension.Provider());
pm.addExtensionProvider("gone","http://jabber.org/protocol/chatstates",
new ChatStateExtension.Provider());
// XHTML
pm.addExtensionProvider("html","http://jabber.org/protocol/xhtml-im",
new XHTMLExtensionProvider());
// group chat invitations
pm.addExtensionProvider("x","jabber:x:conference",
new GroupChatInvitation.Provider());
// service discovery # Items
pm.addIQProvider("query","http://jabber.org/protocol/disco#items",
new DiscoverItemsProvider());
// service discovery # Info
pm.addIQProvider("query","http://jabber.org/protocol/disco#info",
new DiscoverInfoProvider());
// data forms
pm.addExtensionProvider("x","jabber:x:data", new DataFormProvider());
// MUC user
pm.addExtensionProvider("x","http://jabber.org/protocol/muc#user",
new MUCUserProvider());
// MUC admin
pm.addIQProvider("query","http://jabber.org/protocol/muc#admin",
new MUCAdminProvider());
// MUC owner
pm.addIQProvider("query","http://jabber.org/protocol/muc#owner",
new MUCOwnerProvider());
// delayed delivery
pm.addExtensionProvider("x","jabber:x:delay", new DelayInformationProvider());
// version
try {
pm.addIQProvider("query","jabber:iq:version",
Class.forName("org.jivesoftware.smackx.packet.Version"));
} catch (ClassNotFoundException e) {
Log.e(TAG, e.getMessage(), e);
}
// vCard
pm.addIQProvider("vCard","vcard-temp", new VCardProvider());
// offline message requests
pm.addIQProvider("offline","http://jabber.org/protocol/offline",
new OfflineMessageRequest.Provider());
// offline message indicator
pm.addExtensionProvider("offline","http://jabber.org/protocol/offline",
new OfflineMessageInfo.Provider());
// last activity
pm.addIQProvider("query","jabber:iq:last", new LastActivity.Provider());
// user search
pm.addIQProvider("query","jabber:iq:search", new UserSearch.Provider());
// sharedGroupsInfo
pm.addIQProvider("sharedgroup","http://www.jivesoftware.org/protocol/sharedgroup",
new SharedGroupsInfo.Provider());
// JEP-33: Extended Stanza Addressing
pm.addExtensionProvider("addresses","http://jabber.org/protocol/address",
new MultipleAddressesProvider());
// file transfer
pm.addIQProvider("si","http://jabber.org/protocol/si", new StreamInitiationProvider());
pm.addIQProvider("query","http://jabber.org/protocol/bytestreams",
new BytestreamsProvider());
pm.addIQProvider("open","http://jabber.org/protocol/ibb", new BytestreamsProvider());
pm.addIQProvider("close","http://jabber.org/protocol/ibb", new CloseIQProvider());
pm.addExtensionProvider("data","http://jabber.org/protocol/ibb",
new DataPacketProvider());
// privacy
pm.addIQProvider("query","jabber:iq:privacy", new PrivacyProvider());
pm.addIQProvider("command", "http://jabber.org/protocol/commands",
new AdHocCommandDataProvider());
pm.addExtensionProvider("malformed-action", "http://jabber.org/protocol/commands",
new AdHocCommandDataProvider.MalformedActionError());
pm.addExtensionProvider("bad-locale", "http://jabber.org/protocol/commands",
new AdHocCommandDataProvider.BadLocaleError());
pm.addExtensionProvider("bad-payload", "http://jabber.org/protocol/commands",
new AdHocCommandDataProvider.BadPayloadError());
pm.addExtensionProvider("bad-sessionid", "http://jabber.org/protocol/commands",
new AdHocCommandDataProvider.BadSessionIDError());
pm.addExtensionProvider("session-expired", "http://jabber.org/protocol/commands",
new AdHocCommandDataProvider.SessionExpiredError());
}
}