package com.koushikdutta.tabletsms;
import java.util.Hashtable;
import org.json.JSONArray;
import org.json.JSONObject;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ContentValues;
import android.content.Intent;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import com.koushikdutta.async.http.AsyncHttpClient;
import com.koushikdutta.async.http.AsyncHttpGet;
import com.koushikdutta.async.http.AsyncHttpResponse;
public class SyncService extends Service {
private static final String LOGTAG = "TabletSMS";
private Settings mSettings;
private boolean mSyncing = false;
private long mLastSync = 0;
private int mSyncCounter = 0;
private String mAccount;
private SQLiteDatabase mDatabase;
private int mNewMessageCount = 0;
private String mLastMessageNumber;
private String mLastMessageText;
private boolean handleResult(JSONObject result, boolean isPush) {
if (result == null) {
Log.i(LOGTAG, "null result? push limit? bailing...");
return false;
}
int newCounter = 0;
boolean needsSync = false;
if (isPush) {
try {
newCounter = result.getInt("this_last_sync");
if (newCounter != mSyncCounter + 1)
needsSync = true;
mSyncCounter = newCounter;
}
catch (Exception ex) {
needsSync = true;
}
}
// System.out.println(result);
JSONArray data = result.optJSONArray("data");
if (data == null) {
Log.i(LOGTAG, "No data?");
if (isPush)
return needsSync;
finishSync();
return false;
}
if (data.length() == 0) {
Log.i(LOGTAG, "Done syncing.");
if (isPush)
return needsSync;
finishSync();
return false;
}
for (int i = 0; i < data.length(); i++) {
try {
JSONObject message = data.getJSONObject(i);
long date = message.getLong("date");
if (!isPush || !needsSync)
mLastSync = Math.max(mLastSync, date);
ContentValues args = new ContentValues();
String last;
String number;
args.put("key", message.getString("number") + "/" + message.getLong("date"));
args.put("number", number = message.getString("number"));
args.put("date", message.getLong("date"));
args.put("message", last = message.optString("message"));
String type;
args.put("type", type = message.getString("type"));
args.put("image", message.optString("image"));
args.put("unread", "incoming".equals(type) ? 1 : 0);
if ("incoming".equals(type)) {
mNewMessageCount++;
mLastMessageNumber = number;
mLastMessageText = last;
}
mDatabase.replace("sms", null, args);
}
catch (Exception ex) {
ex.printStackTrace();
}
}
mSettings.setLong("last_sync_timestamp", mLastSync);
if (isPush) {
mSettings.setInt("sync_counter", mSyncCounter);
}
if (isPush)
return needsSync;
return true;
}
private void doAuth() {
final Handler handler = new Handler();
new Thread() {
public void run() {
try {
final String account = mSettings.getString("account");
AccountManager accountManager = AccountManager.get(SyncService.this);
Account acct = new Account(account, "com.google");
String curAuthToken = Settings.getInstance(SyncService.this).getString("web_connect_auth_token");
if (!Helper.isJavaScriptNullOrEmpty(curAuthToken))
accountManager.invalidateAuthToken(acct.type, curAuthToken);
Bundle bundle = accountManager.getAuthToken(acct, TickleServiceHelper.AUTH_TOKEN_TYPE, false, null, null).getResult();
final String authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN);
if (!Helper.isJavaScriptNullOrEmpty(authToken)) {
Settings settings = Settings.getInstance(SyncService.this);
settings.setString("web_connect_auth_token", authToken);
}
if (authToken == null) {
Log.e(LOGTAG, "Authentication failure.");
ServiceHelper.createAuthenticationNotification(SyncService.this);
finishSync();
return;
}
String newCookie = TickleServiceHelper.getCookie(SyncService.this);
if (newCookie == null) {
Log.e(LOGTAG, "Authentication failure.");
ServiceHelper.createAuthenticationNotification(SyncService.this);
finishSync();
return;
}
}
catch (Exception ex) {
ex.printStackTrace();
finishSync();
return;
}
handler.post(new Runnable() {
@Override
public void run() {
syncNext();
}
});
};
}.start();
}
boolean mHasAuthFailed = false;
private void syncNext() {
try {
AsyncHttpGet get = new AsyncHttpGet(ServiceHelper.SMS_URL + "?limit=100&after_date=" + mLastSync);
String ascidCookie = mSettings.getString("Cookie");
get.getHeaders().getHeaders().add("Cookie", ascidCookie);
get.getHeaders().getHeaders().add("X-Same-Domain", "1");
get.setFollowRedirect(false);
AsyncHttpClient.getDefaultInstance().execute(get, new AsyncHttpClient.JSONObjectCallback() {
@Override
public void onCompleted(Exception e, AsyncHttpResponse response, JSONObject result) {
if (response != null && response.getHeaders() != null && response.getHeaders().getHeaders() != null && response.getHeaders().getHeaders().getResponseCode() == 302) {
if (mHasAuthFailed) {
finishSync();
return;
}
mHasAuthFailed = true;
doAuth();
return;
}
mHasAuthFailed = false;
if (e != null) {
e.printStackTrace();
finishSync();
return;
}
// System.out.println(result);
if (handleResult(result, false))
syncNext();
}
});
}
catch (Exception e) {
e.printStackTrace();
finishSync();
}
}
private void startSync() {
if (mSyncing)
return;
long oldPurge = System.currentTimeMillis() - 21L * 24L * 60L * 60L * 1000L;
mDatabase.delete("sms", "date < ?", new String[] { String.valueOf(oldPurge) });
mNewMessageCount = 0;
if (Helper.isJavaScriptNullOrEmpty(mAccount)) {
finishSync();
return;
}
syncNext();
mSyncing = true;
}
Hashtable<String, CachedPhoneLookup> mLookup = new Hashtable<String, CachedPhoneLookup>();
public static final int NOTIFICATION_ID = 3948934;
private void doNotifications() {
if (!mSettings.getBoolean("notifications", true))
return;
if (MainActivity.mVisible)
return;
int totalPendingMessages = mSettings.getInt("new_message_count", 0);
NotificationManager nm = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
if (totalPendingMessages == 0) {
nm.cancel(NOTIFICATION_ID);
return;
}
Notification n = new Notification();
PendingIntent pi = PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0);
n.icon = R.drawable.ic_stat_message_notification;
n.defaults = Notification.DEFAULT_ALL;
CachedPhoneLookup lookup = Helper.getPhoneLookup(this, mLookup, mLastMessageNumber);
String name = mLastMessageNumber;
if (lookup != null)
name = lookup.displayName;
n.tickerText = name + ": " + mLastMessageText;
n.flags |= Notification.FLAG_AUTO_CANCEL;
if (totalPendingMessages == 1) {
n.setLatestEventInfo(this, name, mLastMessageText, pi);
}
else {
n.setLatestEventInfo(this, getString(R.string.app_name), getString(R.string.new_messages), pi);
}
nm.notify(NOTIFICATION_ID, n);
}
private void finishSync() {
Log.i(LOGTAG, "Finishing sync.");
int previousMessageCount = mSettings.getInt("new_message_count", 0);
if (mNewMessageCount != 0) {
if (mNewMessageCount == 1) {
// use this to track how many messages a user has gotten after initial sync.
// show a "rate me!" dialog at a certain point.
int prevCounter = mSettings.getInt("usage_single_incoming", 0);
mSettings.setInt("usage_single_incoming", prevCounter + 1);
}
mSettings.setInt("new_message_count", previousMessageCount + mNewMessageCount);
mSettings.setString("last_message_text", mLastMessageText);
mSettings.setString("last_message_number", mLastMessageNumber);
doNotifications();
}
mSettings.setInt("sync_counter", mSyncCounter);
Intent syncComplete = new Intent();
syncComplete.setAction("com.koushikdutta.tabletsms.SYNC_COMPLETE");
sendBroadcast(syncComplete);
mSyncing = false;
stopSelf();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent == null)
return super.onStartCommand(intent, flags, startId);
if ("com.koushikdutta.tabletsms.SYNC".equals(intent.getAction())) {
startSync();
return START_STICKY;
}
else if ("refresh".equals(intent.getStringExtra("type"))) {
try {
if ("sms".equals(intent.getStringExtra("bucket"))) {
if (Helper.isJavaScriptNullOrEmpty(mAccount)) {
return super.onStartCommand(intent, flags, startId);
}
JSONObject envelope = new JSONObject(intent.getStringExtra("envelope"));
mNewMessageCount = 0;
if (handleResult(envelope, true)) {
startSync();
return START_STICKY;
}
else
finishSync();
}
}
catch (Exception ex) {
startSync();
return START_STICKY;
}
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(LOGTAG, "Destroyed sync service.");
mDatabase.close();
}
@Override
public void onCreate() {
super.onCreate();
mSettings = Settings.getInstance(this);
// start from a week ago
mLastSync = mSettings.getLong("last_sync_timestamp", 0);
mLastSync = Math.max(mLastSync, System.currentTimeMillis() - 7L * 24L * 60L * 60L * 1000L);
mSyncCounter = mSettings.getInt("sync_counter", 0);
mDatabase = Database.open(this);
mAccount = mSettings.getString("account");
}
}