diff --git a/res/drawable/ic_menu_clear.png b/res/drawable/ic_menu_clear.png
new file mode 100644
index 000000000..ef076b693
Binary files /dev/null and b/res/drawable/ic_menu_clear.png differ
diff --git a/res/drawable/ic_menu_compact.png b/res/drawable/ic_menu_compact.png
new file mode 100644
index 000000000..ef076b693
Binary files /dev/null and b/res/drawable/ic_menu_compact.png differ
diff --git a/res/menu/accounts_context.xml b/res/menu/accounts_context.xml
index 40139a9d0..e1f569466 100644
--- a/res/menu/accounts_context.xml
+++ b/res/menu/accounts_context.xml
@@ -6,8 +6,13 @@
android:title="@string/empty_trash_action" />
+
+
+
diff --git a/res/menu/folder_message_list_option.xml b/res/menu/folder_message_list_option.xml
index 1c5800848..7417f4ff0 100644
--- a/res/menu/folder_message_list_option.xml
+++ b/res/menu/folder_message_list_option.xml
@@ -65,6 +65,16 @@
android:title="@string/refresh_action"
android:icon="@drawable/ic_menu_refresh"
/>
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 201815f3a..14da92ef2 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -78,6 +78,21 @@
Load up
to %d more
+
+ GB
+ MB
+ KB
+ B
+
+
+ Account \"%s\" shrunk from
+ %s
+ to
+ %s
+
+
+ Compacting account \"%s\"
+ Clearing account \"%s\"
New email
New email from %s
@@ -254,6 +269,9 @@ Welcome to K-9 Mail setup. K-9 is an open source email client for Android based
WebDav(Exchange) before SMTP
Account options
+
+ Compact
+ Clear all data (danger!)
Email checking frequency
diff --git a/src/com/android/email/MessagingController.java b/src/com/android/email/MessagingController.java
index 2637d2aac..1aa5194ba 100644
--- a/src/com/android/email/MessagingController.java
+++ b/src/com/android/email/MessagingController.java
@@ -2487,7 +2487,65 @@ s * critical data as fast as possible, and then we'll fill in the de
}
});
}
+
+ public void compact(final Account account, final MessagingListener ml)
+ {
+ putBackground("compact:" + account.getDescription(), ml, new Runnable()
+ {
+ public void run()
+ {
+ try
+ {
+ LocalStore localStore = (LocalStore)Store.getInstance(account.getLocalStoreUri(), mApplication);
+ long oldSize = localStore.getSize();
+ localStore.compact();
+ long newSize = localStore.getSize();
+ if (ml != null)
+ {
+ ml.accountSizeChanged(account, oldSize, newSize);
+ }
+ for (MessagingListener l : getListeners()) {
+ l.accountSizeChanged(account, oldSize, newSize);
+ l.accountReset(account);
+ }
+ }
+ catch (Exception e)
+ {
+ Log.e(Email.LOG_TAG, "Failed to compact account " + account.getDescription(), e);
+ }
+ }
+ });
+ }
+ public void clear(final Account account, final MessagingListener ml)
+ {
+ putBackground("clear:" + account.getDescription(), ml, new Runnable()
+ {
+ public void run()
+ {
+ try
+ {
+ LocalStore localStore = (LocalStore)Store.getInstance(account.getLocalStoreUri(), mApplication);
+ long oldSize = localStore.getSize();
+ localStore.clear();
+ localStore.resetVisibleLimits(account.getDisplayCount());
+ long newSize = localStore.getSize();
+ if (ml != null)
+ {
+ ml.accountSizeChanged(account, oldSize, newSize);
+ }
+ for (MessagingListener l : getListeners()) {
+ l.accountSizeChanged(account, oldSize, newSize);
+ l.accountReset(account);
+ }
+ }
+ catch (Exception e)
+ {
+ Log.e(Email.LOG_TAG, "Failed to compact account " + account.getDescription(), e);
+ }
+ }
+ });
+ }
public void saveDraft(final Account account, final Message message) {
try {
Store localStore = Store.getInstance(account.getLocalStoreUri(), mApplication);
diff --git a/src/com/android/email/MessagingListener.java b/src/com/android/email/MessagingListener.java
index 3f561568a..5edbe4f3e 100644
--- a/src/com/android/email/MessagingListener.java
+++ b/src/com/android/email/MessagingListener.java
@@ -18,6 +18,14 @@ public class MessagingListener {
public void accountStatusChanged(Account account, int unreadMessageCount) {
}
+
+ public void accountSizeChanged(Account account, long oldSize, long newSize)
+ {
+ }
+
+ public void accountReset(Account account) {
+
+ }
public void listFoldersStarted(Account account) {
}
diff --git a/src/com/android/email/activity/Accounts.java b/src/com/android/email/activity/Accounts.java
index 2ed5fff36..bc31334b0 100644
--- a/src/com/android/email/activity/Accounts.java
+++ b/src/com/android/email/activity/Accounts.java
@@ -29,6 +29,7 @@ import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
+import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
@@ -64,9 +65,17 @@ public class Accounts extends ListActivity implements OnItemClickListener, OnCli
private AccountsHandler mHandler = new AccountsHandler();
private AccountsAdapter mAdapter;
+ private class AccountSizeChangedHolder {
+ Account account;
+ long oldSize;
+ long newSize;
+ }
+
class AccountsHandler extends Handler
{
private static final int DATA_CHANGED = 1;
+ private static final int MSG_ACCOUNT_SIZE_CHANGED = 2;
+ private static final int MSG_WORKING_ACCOUNT = 3;
public void handleMessage(android.os.Message msg)
{
@@ -78,6 +87,29 @@ public class Accounts extends ListActivity implements OnItemClickListener, OnCli
mAdapter.notifyDataSetChanged();
}
break;
+ case MSG_WORKING_ACCOUNT:
+ {
+ Account account = (Account)msg.obj;
+ int res = msg.arg1;
+ String toastText = getString(res, account.getDescription());
+
+ Toast toast = Toast.makeText(getApplication(), toastText, Toast.LENGTH_SHORT);
+ toast.show();
+ break;
+ }
+ case MSG_ACCOUNT_SIZE_CHANGED:
+ {
+ AccountSizeChangedHolder holder = (AccountSizeChangedHolder)msg.obj;
+ Account account = holder.account;
+ Long oldSize = holder.oldSize;
+ Long newSize = holder.newSize;
+ String toastText = getString(R.string.account_size_changed, account.getDescription(),
+ SizeFormatter.formatSize(getApplication(), oldSize), SizeFormatter.formatSize(getApplication(), newSize));;
+
+ Toast toast = Toast.makeText(getApplication(), toastText, Toast.LENGTH_LONG);
+ toast.show();
+ break;
+ }
default:
super.handleMessage(msg);
}
@@ -87,6 +119,28 @@ public class Accounts extends ListActivity implements OnItemClickListener, OnCli
{
sendEmptyMessage(DATA_CHANGED);
}
+
+ public void workingAccount(Account account, int res)
+ {
+ android.os.Message msg = new android.os.Message();
+ msg.what = MSG_WORKING_ACCOUNT;
+ msg.obj = account;
+ msg.arg1 = res;
+
+ sendMessage(msg);
+ }
+
+ public void accountSizeChanged(Account account, long oldSize, long newSize)
+ {
+ android.os.Message msg = new android.os.Message();
+ msg.what = MSG_ACCOUNT_SIZE_CHANGED;
+ AccountSizeChangedHolder holder = new AccountSizeChangedHolder();
+ holder.account = account;
+ holder.oldSize = oldSize;
+ holder.newSize = newSize;
+ msg.obj = holder;
+ sendMessage(msg);
+ }
}
@@ -98,6 +152,14 @@ public class Accounts extends ListActivity implements OnItemClickListener, OnCli
mHandler.dataChanged();
}
+ @Override
+ public void accountSizeChanged(Account account, long oldSize, long newSize)
+ {
+
+ mHandler.accountSizeChanged(account, oldSize, newSize);
+
+ }
+
@Override
public void synchronizeMailboxFinished(
Account account,
@@ -275,9 +337,28 @@ public class Accounts extends ListActivity implements OnItemClickListener, OnCli
case R.id.empty_trash:
onEmptyTrash(account);
break;
+ case R.id.compact:
+ onCompact(account);
+ break;
+ case R.id.clear:
+ onClear(account);
+ break;
}
return true;
}
+
+ private void onCompact(Account account)
+ {
+ mHandler.workingAccount(account, R.string.compacting_account);
+ MessagingController.getInstance(getApplication()).compact(account, null);
+ }
+
+ private void onClear(Account account)
+ {
+ mHandler.workingAccount(account, R.string.clearing_account);
+ MessagingController.getInstance(getApplication()).clear(account, null);
+ }
+
public void onItemClick(AdapterView parent, View view, int position, long id) {
Account account = (Account)parent.getItemAtPosition(position);
diff --git a/src/com/android/email/activity/FolderMessageList.java b/src/com/android/email/activity/FolderMessageList.java
index 6ce637ffb..ac7ef335c 100644
--- a/src/com/android/email/activity/FolderMessageList.java
+++ b/src/com/android/email/activity/FolderMessageList.java
@@ -191,6 +191,8 @@ public class FolderMessageList extends ExpandableListActivity
dateFormat = null;
timeFormat = null;
}
+
+
class FolderMessageListHandler extends Handler
{
@@ -211,6 +213,10 @@ public class FolderMessageList extends ExpandableListActivity
private static final int MSG_FOLDER_SYNCING = 18;
private static final int MSG_SENDING_OUTBOX = 19;
+
+ private static final int MSG_ACCOUNT_SIZE_CHANGED = 20;
+
+ private static final int MSG_WORKING_ACCOUNT = 21;
@Override
public void handleMessage(android.os.Message msg)
{
@@ -249,6 +255,27 @@ public class FolderMessageList extends ExpandableListActivity
mAdapter.notifyDataSetChanged();
break;
}
+ case MSG_ACCOUNT_SIZE_CHANGED:
+ {
+ Long[] sizes = (Long[])msg.obj;
+ String toastText = getString(R.string.account_size_changed, mAccount.getDescription(),
+ SizeFormatter.formatSize(getApplication(), sizes[0]), SizeFormatter.formatSize(getApplication(), sizes[1]));;
+
+ Toast toast = Toast.makeText(getApplication(), toastText, Toast.LENGTH_LONG);
+ toast.show();
+ break;
+ }
+ case MSG_WORKING_ACCOUNT:
+ {
+ int res = msg.arg1;
+ String toastText = getString(res, mAccount.getDescription());
+
+ Toast toast = Toast.makeText(getApplication(), toastText, Toast.LENGTH_SHORT);
+ toast.show();
+ break;
+ }
+
+
case MSG_SYNC_MESSAGES:
{
FolderInfoHolder folder = (FolderInfoHolder) ((Object[]) msg.obj)[0];
@@ -312,6 +339,15 @@ public class FolderMessageList extends ExpandableListActivity
{ folder, messages };
sendMessage(msg);
}
+
+ public void workingAccount(int res)
+ {
+ android.os.Message msg = new android.os.Message();
+ msg.what = MSG_WORKING_ACCOUNT;
+ msg.arg1 = res;
+
+ sendMessage(msg);
+ }
public void removeMessage(FolderInfoHolder folder, MessageInfoHolder message)
{
@@ -321,6 +357,14 @@ public class FolderMessageList extends ExpandableListActivity
{ folder, message };
sendMessage(msg);
}
+
+ public void accountSizeChanged(long oldSize, long newSize)
+ {
+ android.os.Message msg = new android.os.Message();
+ msg.what = MSG_ACCOUNT_SIZE_CHANGED;
+ msg.obj = new Long[] { oldSize, newSize };
+ sendMessage(msg);
+ }
public void folderLoading(String folder, boolean loading)
{
@@ -587,6 +631,8 @@ public class FolderMessageList extends ExpandableListActivity
MessagingController.getInstance(getApplication()).addListener(
mAdapter.mListener);
mAccount.refresh(Preferences.getPreferences(this));
+ markAllRefresh();
+
onRefresh(false);
NotificationManager notifMgr = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
@@ -628,7 +674,8 @@ public class FolderMessageList extends ExpandableListActivity
*/
int position = mListView.getFlatListPosition(ExpandableListView
.getPackedPositionForGroup(groupPosition));
- mListView.setSelectionFromTop(position, 0);
+
+ mListView.setSelectionFromTop(position, 0);
}
final FolderInfoHolder folder = (FolderInfoHolder) mAdapter
@@ -814,6 +861,11 @@ public class FolderMessageList extends ExpandableListActivity
}
+ private void markAllRefresh()
+ {
+ mAdapter.mListener.accountReset(mAccount);
+ }
+
private void onCycleSort()
{
SORT_TYPE[] sorts = SORT_TYPE.values();
@@ -975,11 +1027,28 @@ public class FolderMessageList extends ExpandableListActivity
case R.id.empty_trash:
onEmptyTrash(mAccount);
return true;
+ case R.id.compact:
+ onCompact(mAccount);
+ return true;
+ case R.id.clear:
+ onClear(mAccount);
+ return true;
default:
return super.onOptionsItemSelected(item);
}
}
+
+ private void onCompact(Account account)
+ {
+ mHandler.workingAccount(R.string.compacting_account);
+ MessagingController.getInstance(getApplication()).compact(account, null);
+ }
+ private void onClear(Account account)
+ {
+ mHandler.workingAccount(R.string.clearing_account);
+ MessagingController.getInstance(getApplication()).clear(account, null);
+ }
@Override
public boolean onCreateOptionsMenu(Menu menu)
@@ -1186,6 +1255,18 @@ public class FolderMessageList extends ExpandableListActivity
}
mHandler.dataChanged();
}
+ @Override
+ public void accountReset(Account account)
+ {
+ if (!account.equals(mAccount))
+ {
+ return;
+ }
+ for (FolderInfoHolder folder : mFolders)
+ {
+ folder.needsRefresh = true;
+ }
+ }
@Override
public void listFolders(Account account, Folder[] folders)
@@ -1484,6 +1565,16 @@ public class FolderMessageList extends ExpandableListActivity
}
mHandler.sendingOutbox(false);
}
+
+ public void accountSizeChanged(Account account, long oldSize, long newSize)
+ {
+ if (!account.equals(mAccount))
+ {
+ return;
+ }
+ mHandler.accountSizeChanged(oldSize, newSize);
+
+ }
@Override
public void messageUidChanged(Account account, String folder,
@@ -1807,19 +1898,20 @@ public class FolderMessageList extends ExpandableListActivity
{
holder.main.setText(getString(R.string.status_loading_more));
holder.progress.setVisibility(View.VISIBLE);
- } else
+ }
+ else
{
if (folder.lastCheckFailed == false)
{
holder.main.setText(String.format(getString(R.string.load_more_messages_fmt).toString(),
mAccount.getDisplayCount()));
- } else
+ }
+ else
{
- holder.main.setText(getString(R.string.status_loading_more_failed));
- }
+ holder.main.setText(getString(R.string.status_loading_more_failed));
+ }
holder.progress.setVisibility(View.GONE);
- }
- return view;
+ } return view;
} else
{
MessageInfoHolder message = (MessageInfoHolder) getChild(groupPosition,
diff --git a/src/com/android/email/mail/store/LocalStore.java b/src/com/android/email/mail/store/LocalStore.java
index cdd4c1171..05df42aba 100644
--- a/src/com/android/email/mail/store/LocalStore.java
+++ b/src/com/android/email/mail/store/LocalStore.java
@@ -140,7 +140,88 @@ public class LocalStore extends Store implements Serializable {
throw new Error("Database upgrade failed!");
}
}
+
+ public long getSize()
+ {
+ long attachmentLength = 0;
+
+ File[] files = mAttachmentsDir.listFiles();
+ for (File file : files) {
+ if (file.exists()) {
+ attachmentLength += file.length();
+ }
+ }
+
+
+ File dbFile = new File(mPath);
+ return dbFile.length() + attachmentLength;
+ }
+
+ public void compact() throws MessagingException
+ {
+ Log.i(Email.LOG_TAG, "Before prune size = " + getSize());
+
+ pruneCachedAttachments();
+ Log.i(Email.LOG_TAG, "After prune / before compaction size = " + getSize());
+
+ mDb.execSQL("VACUUM");
+ Log.i(Email.LOG_TAG, "After compaction size = " + getSize());
+ }
+
+ public void clear() throws MessagingException
+ {
+ Log.i(Email.LOG_TAG, "Before prune size = " + getSize());
+
+ pruneCachedAttachments(true);
+
+ Log.i(Email.LOG_TAG, "After prune / before compaction size = " + getSize());
+
+ Log.i(Email.LOG_TAG, "Before clear folder count = " + getFolderCount());
+ Log.i(Email.LOG_TAG, "Before clear message count = " + getMessageCount());
+
+ Log.i(Email.LOG_TAG, "After prune / before clear size = " + getSize());
+ // don't delete messages that are Local, since there is no copy on the server.
+ // Don't delete deleted messages. They are essentially placeholders for UIDs of messages that have
+ // been deleted locally. They take up no space, and are indicated with a null date.
+ mDb.execSQL("DELETE FROM messages WHERE date is not null and uid not like 'Local%'" );
+
+ compact();
+ Log.i(Email.LOG_TAG, "After clear message count = " + getMessageCount());
+
+ Log.i(Email.LOG_TAG, "After clear size = " + getSize());
+ }
+
+ public int getMessageCount() throws MessagingException {
+ Cursor cursor = null;
+ try {
+ cursor = mDb.rawQuery("SELECT COUNT(*) FROM messages", null);
+ cursor.moveToFirst();
+ int messageCount = cursor.getInt(0);
+ return messageCount;
+ }
+ finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ public int getFolderCount() throws MessagingException {
+ Cursor cursor = null;
+ try {
+ cursor = mDb.rawQuery("SELECT COUNT(*) FROM folders", null);
+ cursor.moveToFirst();
+ int messageCount = cursor.getInt(0);
+ return messageCount;
+ }
+ finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
@Override
public LocalFolder getFolder(String name) throws MessagingException {
return new LocalFolder(name);
@@ -204,49 +285,68 @@ public class LocalStore extends Store implements Serializable {
}
}
+ public void pruneCachedAttachments() throws MessagingException {
+ pruneCachedAttachments(false);
+ }
+
/**
* Deletes all cached attachments for the entire store.
*/
- public void pruneCachedAttachments() throws MessagingException {
+ public void pruneCachedAttachments(boolean force) throws MessagingException {
+
+ if (force)
+ {
+ ContentValues cv = new ContentValues();
+ cv.putNull("content_uri");
+ mDb.update("attachments", cv, null, null);
+ }
File[] files = mAttachmentsDir.listFiles();
for (File file : files) {
if (file.exists()) {
+ if (!force) {
+ Cursor cursor = null;
try {
- Cursor cursor = null;
- try {
- cursor = mDb.query(
- "attachments",
- new String[] { "store_data" },
- "id = ?",
- new String[] { file.getName() },
- null,
- null,
- null);
- if (cursor.moveToNext()) {
- if (cursor.getString(0) == null) {
- /*
- * If the attachment has no store data it is not recoverable, so
- * we won't delete it.
- */
- continue;
- }
- }
+ cursor = mDb.query(
+ "attachments",
+ new String[] { "store_data" },
+ "id = ?",
+ new String[] { file.getName() },
+ null,
+ null,
+ null);
+ if (cursor.moveToNext()) {
+ if (cursor.getString(0) == null) {
+ Log.d(Email.LOG_TAG, "Attachment " + file.getAbsolutePath() + " has no store data, not deleting");
+ /*
+ * If the attachment has no store data it is not recoverable, so
+ * we won't delete it.
+ */
+ continue;
+ }
+ }
+ }
+ finally {
+ if (cursor != null) {
+ cursor.close();
}
- finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- ContentValues cv = new ContentValues();
- cv.putNull("content_uri");
- mDb.update("attachments", cv, "id = ?", new String[] { file.getName() });
+ }
+ }
+ if (!force)
+ {
+ try
+ {
+ ContentValues cv = new ContentValues();
+ cv.putNull("content_uri");
+ mDb.update("attachments", cv, "id = ?", new String[] { file.getName() });
}
catch (Exception e) {
- /*
- * If the row has gone away before we got to mark it not-downloaded that's
- * okay.
- */
- }
+ /*
+ * If the row has gone away before we got to mark it not-downloaded that's
+ * okay.
+ */
+ }
+ }
+ Log.d(Email.LOG_TAG, "Deleting attachment " + file.getAbsolutePath() + ", which is of size " + file.length());
if (!file.delete()) {
file.deleteOnExit();
}
@@ -1292,7 +1392,9 @@ public class LocalStore extends Store implements Serializable {
/*
* Delete all of the messages' content to save space.
*/
- mDb.execSQL(
+ ((LocalFolder) mFolder).deleteAttachments(getUid());
+
+ mDb.execSQL(
"UPDATE messages SET " +
"subject = NULL, " +
"sender_list = NULL, " +
@@ -1307,12 +1409,11 @@ public class LocalStore extends Store implements Serializable {
new Object[] {
mId
});
-
- ((LocalFolder) mFolder).deleteAttachments(getUid());
-
+
/*
* Delete all of the messages' attachments to save space.
*/
+ // shouldn't the trigger take care of this? -- danapple
mDb.execSQL("DELETE FROM attachments WHERE id = ?",
new Object[] {
mId