diff --git a/res/values/strings.xml b/res/values/strings.xml
index 906f91924c..16a463b4e4 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -39,7 +39,12 @@
You need to have entered your passphrase before importing keys...
You need to have entered your passphrase before managing keys...
You haven\'t set a passphrase yet!
-
+ Conversation length limit
+ messages per conversation
+ Delete all old messages now?
+ Are you sure you would like to immediately trim all conversation threads to the %s most recent messages?
+ Delete
+
Picture
@@ -377,6 +382,17 @@
MMSC URL (Required)
MMS Proxy Host (Optional)
MMS Proxy Port (Optional)
+ Delivery Reports
+ Request a delivery report for each SMS message you send
+ Request a delivery report for each MMS message you send
+ MMS delivery reports
+ SMS delivery reports
+ Automatically delete older messages once a conversation thread exceeds a specified length
+ Delete old messages
+ Storage
+ Conversation length limit
+ Trim all threads now
+ Scan through all conversation threads and enforce conversation length limits
diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml
index 1b15a6546f..9f9ddb1f6a 100644
--- a/res/xml/preferences.xml
+++ b/res/xml/preferences.xml
@@ -14,17 +14,36 @@
-
+
+ android:summary="@string/preferences__request_a_delivery_report_for_each_sms_message_you_send"
+ android:title="@string/preferences__sms_delivery_reports" />
+ android:title="@string/preferences__mms_delivery_reports" />
+
+
+
+
+
+
+
+
+
diff --git a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java
index b718adb693..67f679e655 100644
--- a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java
+++ b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java
@@ -17,6 +17,8 @@
package org.thoughtcrime.securesms;
import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
@@ -25,6 +27,7 @@ import android.preference.EditTextPreference;
import android.preference.Preference;
import android.preference.PreferenceManager;
import android.provider.ContactsContract;
+import android.util.Log;
import android.widget.Toast;
import com.actionbarsherlock.app.SherlockPreferenceActivity;
@@ -38,6 +41,7 @@ import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.MemoryCleaner;
+import org.thoughtcrime.securesms.util.Trimmer;
import java.util.List;
@@ -83,6 +87,10 @@ public class ApplicationPreferencesActivity extends SherlockPreferenceActivity {
public static final String SMS_DELIVERY_REPORT_PREF = "pref_delivery_report_sms";
public static final String MMS_DELIVERY_REPORT_PREF = "pref_delivery_report_mms";
+ public static final String THREAD_TRIM_ENABLED = "pref_trim_threads";
+ public static final String THREAD_TRIM_LENGTH = "pref_trim_length";
+ public static final String THREAD_TRIM_NOW = "pref_trim_now";
+
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
@@ -104,6 +112,10 @@ public class ApplicationPreferencesActivity extends SherlockPreferenceActivity {
.setOnPreferenceClickListener(new ManageIdentitiesClickListener());
this.findPreference(CHANGE_PASSPHRASE_PREF)
.setOnPreferenceClickListener(new ChangePassphraseClickListener());
+ this.findPreference(THREAD_TRIM_NOW)
+ .setOnPreferenceClickListener(new TrimNowClickListener());
+ this.findPreference(THREAD_TRIM_LENGTH)
+ .setOnPreferenceChangeListener(new TrimLengthValidationListener());
}
@Override
@@ -159,20 +171,16 @@ public class ApplicationPreferencesActivity extends SherlockPreferenceActivity {
preference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference pref, Object newValue) {
- preference.setSummary(newValue == null ? "Not set" : (String)newValue);
+ preference.setSummary(newValue == null ? "Not set" : ((String)newValue));
return true;
}
});
}
private void initializeEditTextSummaries() {
- final EditTextPreference mmscUrlPreference = (EditTextPreference)this.findPreference(MMSC_HOST_PREF);
- final EditTextPreference mmsProxyHostPreference = (EditTextPreference)this.findPreference(MMSC_PROXY_HOST_PREF);
- final EditTextPreference mmsProxyPortPreference = (EditTextPreference)this.findPreference(MMSC_PROXY_PORT_PREF);
-
- initializeEditTextSummary(mmscUrlPreference);
- initializeEditTextSummary(mmsProxyHostPreference);
- initializeEditTextSummary(mmsProxyPortPreference);
+ initializeEditTextSummary((EditTextPreference)this.findPreference(MMSC_HOST_PREF));
+ initializeEditTextSummary((EditTextPreference)this.findPreference(MMSC_PROXY_HOST_PREF));
+ initializeEditTextSummary((EditTextPreference)this.findPreference(MMSC_PROXY_PORT_PREF));
}
private void initializeIdentitySelection() {
@@ -330,4 +338,56 @@ public class ApplicationPreferencesActivity extends SherlockPreferenceActivity {
}
}
+ private class TrimNowClickListener implements Preference.OnPreferenceClickListener {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ final int threadLengthLimit = Integer.parseInt(PreferenceManager.getDefaultSharedPreferences(ApplicationPreferencesActivity.this)
+ .getString(THREAD_TRIM_LENGTH, "500"));
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(ApplicationPreferencesActivity.this);
+ builder.setTitle(R.string.ApplicationPreferencesActivity_delete_all_old_messages_now);
+ builder.setMessage(String.format(getString(R.string.ApplicationPreferencesActivity_are_you_sure_you_would_like_to_immediately_trim_all_conversation_threads_to_the_s_most_recent_messages),
+ threadLengthLimit));
+ builder.setPositiveButton(R.string.ApplicationPreferencesActivity_delete,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Trimmer.trimAllThreads(ApplicationPreferencesActivity.this, threadLengthLimit);
+ }
+ });
+
+ builder.setNegativeButton(android.R.string.cancel, null);
+ builder.show();
+
+ return true;
+ }
+ }
+
+ private class TrimLengthValidationListener implements Preference.OnPreferenceChangeListener {
+
+ public TrimLengthValidationListener() {
+ EditTextPreference preference = (EditTextPreference)findPreference(THREAD_TRIM_LENGTH);
+ preference.setSummary(preference.getText() + " " + getString(R.string.ApplicationPreferencesActivity_messages_per_conversation));
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (newValue == null || ((String)newValue).trim().length() == 0) {
+ return false;
+ }
+
+ try {
+ Integer.parseInt((String)newValue);
+ } catch (NumberFormatException nfe) {
+ Log.w("ApplicationPreferencesActivity", nfe);
+ return false;
+ }
+
+ preference.setSummary((String)newValue + " " +
+ getString(R.string.ApplicationPreferencesActivity_messages_per_conversation));
+ return true;
+ }
+
+ }
+
}
diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java
index a4f1ecf840..d93a019cfb 100644
--- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java
@@ -28,6 +28,7 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
+import org.thoughtcrime.securesms.util.Trimmer;
import ws.com.google.android.mms.InvalidHeaderValueException;
import ws.com.google.android.mms.MmsException;
@@ -343,6 +344,7 @@ public class MmsDatabase extends Database {
notifyConversationListeners(threadId);
DatabaseFactory.getThreadDatabase(context).update(threadId);
DatabaseFactory.getThreadDatabase(context).setUnread(threadId);
+ Trimmer.trimThread(context, threadId);
return messageId;
} catch (RecipientFormattingException rfe) {
@@ -364,6 +366,8 @@ public class MmsDatabase extends Database {
long messageId = insertMediaMessage(sendRequest, contentValues);
DatabaseFactory.getThreadDatabase(context).setRead(threadId);
+ Trimmer.trimThread(context, threadId);
+
return messageId;
}
@@ -427,6 +431,35 @@ public class MmsDatabase extends Database {
}
}
+ /*package*/void deleteMessagesInThreadBeforeDate(long threadId, long date) {
+ date = date / 1000;
+ Cursor cursor = null;
+
+ try {
+ SQLiteDatabase db = databaseHelper.getReadableDatabase();
+ String where = THREAD_ID + " = ? AND (CASE " + MESSAGE_BOX;
+
+ for (int outgoingType : Types.OUTGOING_MAILBOX_TYPES) {
+ where += " WHEN " + outgoingType + " THEN " + DATE_SENT + " < " + date;
+ }
+
+ where += (" ELSE " + DATE_RECEIVED + " < " + date + " END)");
+
+ Log.w("MmsDatabase", "Executing trim query: " + where);
+ cursor = db.query(TABLE_NAME, new String[] {ID}, where, new String[] {threadId+""}, null, null, null);
+
+ while (cursor != null && cursor.moveToNext()) {
+ Log.w("MmsDatabase", "Trimming: " + cursor.getLong(0));
+ delete(cursor.getLong(0));
+ }
+
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+ }
+
+
public void deleteAllThreads() {
DatabaseFactory.getPartDatabase(context).deleteAllParts();
DatabaseFactory.getMmsAddressDatabase(context).deleteAllAddresses();
@@ -553,12 +586,23 @@ public class MmsDatabase extends Database {
public static final int DOWNLOAD_SOFT_FAILURE = 4;
public static final int DOWNLOAD_HARD_FAILURE = 5;
+ public static final int[] OUTGOING_MAILBOX_TYPES = {Types.MESSAGE_BOX_OUTBOX,
+ Types.MESSAGE_BOX_SENT,
+ Types.MESSAGE_BOX_SECURE_OUTBOX,
+ Types.MESSAGE_BOX_SENT_FAILED,
+ Types.MESSAGE_BOX_SECURE_SENT};
+
public static boolean isSecureMmsBox(long mailbox) {
return mailbox == Types.MESSAGE_BOX_SECURE_OUTBOX || mailbox == Types.MESSAGE_BOX_SECURE_SENT || mailbox == Types.MESSAGE_BOX_SECURE_INBOX;
}
public static boolean isOutgoingMmsBox(long mailbox) {
- return mailbox == Types.MESSAGE_BOX_OUTBOX || mailbox == Types.MESSAGE_BOX_SENT || mailbox == Types.MESSAGE_BOX_SECURE_OUTBOX || mailbox == Types.MESSAGE_BOX_SENT_FAILED || mailbox == Types.MESSAGE_BOX_SECURE_SENT;
+ for (int outgoingMailboxType : OUTGOING_MAILBOX_TYPES) {
+ if (mailbox == outgoingMailboxType)
+ return true;
+ }
+
+ return false;
}
public static boolean isPendingMmsBox(long mailbox) {
diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java
index 11c3dfe0e7..1ca27d9243 100644
--- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java
@@ -27,6 +27,7 @@ import android.util.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
+import org.thoughtcrime.securesms.util.Trimmer;
import java.util.ArrayList;
import java.util.List;
@@ -112,6 +113,8 @@ public class SmsDatabase extends Database {
DatabaseFactory.getThreadDatabase(context).setUnread(threadId);
DatabaseFactory.getThreadDatabase(context).update(threadId);
notifyConversationListeners(threadId);
+ Trimmer.trimThread(context, threadId);
+
return messageId;
}
@@ -235,6 +238,8 @@ public class SmsDatabase extends Database {
DatabaseFactory.getThreadDatabase(context).setRead(threadId);
DatabaseFactory.getThreadDatabase(context).update(threadId);
notifyConversationListeners(threadId);
+ Trimmer.trimThread(context, threadId);
+
return messageId;
}
@@ -276,6 +281,18 @@ public class SmsDatabase extends Database {
db.delete(TABLE_NAME, THREAD_ID + " = ?", new String[] {threadId+""});
}
+ /*package*/void deleteMessagesInThreadBeforeDate(long threadId, long date) {
+ SQLiteDatabase db = databaseHelper.getWritableDatabase();
+ String where = THREAD_ID + " = ? AND (CASE " + TYPE;
+
+ for (int outgoingType : Types.OUTGOING_MESSAGE_TYPES) {
+ where += " WHEN " + outgoingType + " THEN " + DATE_SENT + " < " + date;
+ }
+
+ where += (" ELSE " + DATE_RECEIVED + " < " + date + " END)");
+
+ db.delete(TABLE_NAME, where, new String[] {threadId+""});
+ }
/*package*/ void deleteThreads(Set threadIds) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
@@ -348,12 +365,21 @@ public class SmsDatabase extends Database {
public static final int DECRYPT_IN_PROGRESS_TYPE = 47; // Messages are in the process of being asymmetricaly decrypted.
public static final int NO_SESSION_TYPE = 48; // Messages were received with async encryption but there is no session yet.
+ public static final int[] OUTGOING_MESSAGE_TYPES = {SENT_TYPE, SENT_PENDING, ENCRYPTING_TYPE,
+ ENCRYPTED_OUTBOX_TYPE, SECURE_SENT_TYPE,
+ FAILED_TYPE};
+
public static boolean isFailedMessageType(long type) {
return type == FAILED_TYPE;
}
public static boolean isOutgoingMessageType(long type) {
- return type == SENT_TYPE || type == SENT_PENDING || type == ENCRYPTING_TYPE || type == ENCRYPTED_OUTBOX_TYPE || type == SECURE_SENT_TYPE || type == FAILED_TYPE;
+ for (int outgoingType : OUTGOING_MESSAGE_TYPES) {
+ if (type == outgoingType)
+ return true;
+ }
+
+ return false;
}
public static boolean isPendingMessageType(long type) {
diff --git a/src/org/thoughtcrime/securesms/database/ThreadDatabase.java b/src/org/thoughtcrime/securesms/database/ThreadDatabase.java
index c4511e8fed..8f6def255c 100644
--- a/src/org/thoughtcrime/securesms/database/ThreadDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/ThreadDatabase.java
@@ -21,6 +21,7 @@ import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
@@ -143,6 +144,56 @@ public class ThreadDatabase extends Database {
notifyConversationListListeners();
}
+ public void trimAllThreads(int length, ProgressListener listener) {
+ Cursor cursor = null;
+ int threadCount = 0;
+ int complete = 0;
+
+ try {
+ cursor = this.getConversationList();
+
+ if (cursor != null)
+ threadCount = cursor.getCount();
+
+ while (cursor != null && cursor.moveToNext()) {
+ long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
+ trimThread(threadId, length);
+
+ listener.onProgress(++complete, threadCount);
+ }
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+ }
+
+ public void trimThread(long threadId, int length) {
+ Log.w("ThreadDatabase", "Trimming thread: " + threadId + " to: " + length);
+ Cursor cursor = null;
+
+ try {
+ cursor = DatabaseFactory.getMmsSmsDatabase(context).getConversation(threadId);
+
+ if (cursor != null && cursor.getCount() > length) {
+ Log.w("ThreadDatabase", "Cursor count is greater than length!");
+ cursor.moveToPosition(cursor.getCount() - length);
+
+ long lastTweetDate = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsDatabase.DATE_RECEIVED));
+
+ Log.w("ThreadDatabase", "Cut off tweet date: " + lastTweetDate);
+
+ DatabaseFactory.getSmsDatabase(context).deleteMessagesInThreadBeforeDate(threadId, lastTweetDate);
+ DatabaseFactory.getMmsDatabase(context).deleteMessagesInThreadBeforeDate(threadId, lastTweetDate);
+
+ update(threadId);
+ notifyConversationListeners(threadId);
+ }
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+ }
+
public void setRead(long threadId) {
ContentValues contentValues = new ContentValues(1);
contentValues.put(READ, 1);
@@ -290,4 +341,8 @@ public class ThreadDatabase extends Database {
notifyConversationListListeners();
}
+
+ public static interface ProgressListener {
+ public void onProgress(int complete, int total);
+ }
}
diff --git a/src/org/thoughtcrime/securesms/util/Trimmer.java b/src/org/thoughtcrime/securesms/util/Trimmer.java
new file mode 100644
index 0000000000..021c7fb5eb
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/util/Trimmer.java
@@ -0,0 +1,83 @@
+package org.thoughtcrime.securesms.util;
+
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.AsyncTask;
+import android.preference.PreferenceManager;
+import android.widget.Toast;
+
+import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
+import org.thoughtcrime.securesms.database.DatabaseFactory;
+import org.thoughtcrime.securesms.database.ThreadDatabase;
+
+public class Trimmer {
+
+ public static void trimAllThreads(Context context, int threadLengthLimit) {
+ new TrimmingProgressTask(context).execute(threadLengthLimit);
+ }
+
+ public static void trimThread(final Context context, final long threadId) {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ boolean trimmingEnabled = preferences.getBoolean(ApplicationPreferencesActivity.THREAD_TRIM_ENABLED, false);
+ final int threadLengthLimit = Integer.parseInt(preferences.getString(ApplicationPreferencesActivity.THREAD_TRIM_LENGTH, "500"));
+
+ if (!trimmingEnabled)
+ return;
+
+ new Thread() {
+ @Override
+ public void run() {
+ DatabaseFactory.getThreadDatabase(context).trimThread(threadId, threadLengthLimit);
+ }
+ }.start();
+ }
+
+ private static class TrimmingProgressTask extends AsyncTask implements ThreadDatabase.ProgressListener {
+ private ProgressDialog progressDialog;
+ private Context context;
+
+ public TrimmingProgressTask(Context context) {
+ this.context = context;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ progressDialog = new ProgressDialog(context);
+ progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
+ progressDialog.setCancelable(false);
+ progressDialog.setIndeterminate(false);
+ progressDialog.setTitle("Deleting...");
+ progressDialog.setMessage("Deleting old messages...");
+ progressDialog.setMax(100);
+ progressDialog.show();
+ }
+
+ @Override
+ protected Void doInBackground(Integer... params) {
+ DatabaseFactory.getThreadDatabase(context).trimAllThreads(params[0], this);
+ return null;
+ }
+
+ @Override
+ protected void onProgressUpdate(Integer... progress) {
+ double count = progress[1];
+ double index = progress[0];
+
+ progressDialog.setProgress((int)Math.round((index / count) * 100.0));
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ progressDialog.dismiss();
+ Toast.makeText(context,
+ "Old messages successfully deleted!",
+ Toast.LENGTH_LONG).show();
+ }
+
+ @Override
+ public void onProgress(int complete, int total) {
+ this.publishProgress(complete, total);
+ }
+ }
+}