Reenable delete-message UI and write delete-from-provider.
* Longpress delete in MessageList * Delete or Menu-Delete in MessageView * Add deleteMessage() to Controller * Unit tests for the new code in Controller
This commit is contained in:
parent
28448e782b
commit
7c3cca80a0
|
@ -19,8 +19,14 @@ package com.android.email;
|
|||
import com.android.email.mail.MessagingException;
|
||||
import com.android.email.mail.Store;
|
||||
import com.android.email.provider.EmailContent;
|
||||
import com.android.email.provider.EmailContent.Mailbox;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
|
@ -33,18 +39,32 @@ public class Controller {
|
|||
|
||||
static Controller sInstance;
|
||||
private Context mContext;
|
||||
private Context mProviderContext;
|
||||
private MessagingController mLegacyController;
|
||||
private HashSet<Result> mListeners = new HashSet<Result>();
|
||||
|
||||
private static String[] MESSAGEID_TO_ACCOUNTID_PROJECTION = new String[] {
|
||||
EmailContent.RECORD_ID,
|
||||
EmailContent.MessageColumns.ACCOUNT_KEY
|
||||
};
|
||||
private static int MESSAGEID_TO_ACCOUNTID_COLUMN_ACCOUNTID = 1;
|
||||
|
||||
private static String[] ACCOUNTID_TO_MAILBOXTYPE_PROJECTION = new String[] {
|
||||
EmailContent.RECORD_ID,
|
||||
EmailContent.MailboxColumns.ACCOUNT_KEY,
|
||||
EmailContent.MailboxColumns.TYPE
|
||||
};
|
||||
private static int ACCOUNTID_TO_MAILBOXTYPE_COLUMN_ID = 0;
|
||||
|
||||
protected Controller(Context _context) {
|
||||
mContext = _context;
|
||||
mProviderContext = _context;
|
||||
mLegacyController = MessagingController.getInstance(mContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or creates the singleton instance of Controller. Application is used to
|
||||
* provide a Context to classes that need it.
|
||||
* @param _context
|
||||
* Gets or creates the singleton instance of Controller.
|
||||
* @param _context The context that will be used for all underlying system access
|
||||
*/
|
||||
public synchronized static Controller getInstance(Context _context) {
|
||||
if (sInstance == null) {
|
||||
|
@ -53,6 +73,15 @@ public class Controller {
|
|||
return sInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* For testing only: Inject a different context for provider access. This will be
|
||||
* used internally for access the underlying provider (e.g. getContentResolver().query()).
|
||||
* @param providerContext the provider context to be used by this instance
|
||||
*/
|
||||
public void setProviderContext(Context providerContext) {
|
||||
mProviderContext = providerContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Any UI code that wishes for callback results (on async ops) should register their callback
|
||||
* here (typically from onResume()). Unregistered callbacks will never be called, to prevent
|
||||
|
@ -148,6 +177,95 @@ public class Controller {
|
|||
}.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a single message by moving it to the trash.
|
||||
*
|
||||
* This function has no callback, no result reporting, because the desired outcome
|
||||
* is reflected entirely by changes to one or more cursors.
|
||||
*
|
||||
* @param messageId The id of the message to "delete".
|
||||
* @param accountId The id of the message's account, or -1 if not known by caller
|
||||
*
|
||||
* TODO: Move out of UI thread
|
||||
* TODO: "get account a for message m" should be a utility
|
||||
* TODO: "get mailbox of type n for account a" should be a utility
|
||||
*/
|
||||
public void deleteMessage(long messageId, long accountId) {
|
||||
ContentResolver resolver = mProviderContext.getContentResolver();
|
||||
|
||||
// 1. Look up acct# for message we're deleting
|
||||
Cursor c = null;
|
||||
if (accountId == -1) {
|
||||
try {
|
||||
c = resolver.query(EmailContent.Message.CONTENT_URI,
|
||||
MESSAGEID_TO_ACCOUNTID_PROJECTION, EmailContent.RECORD_ID + "=?",
|
||||
new String[] { Long.toString(messageId) }, null);
|
||||
if (c.moveToFirst()) {
|
||||
accountId = c.getLong(MESSAGEID_TO_ACCOUNTID_COLUMN_ACCOUNTID);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
if (c != null) c.close();
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Confirm that there is a trash mailbox available
|
||||
long trashMailboxId = -1;
|
||||
c = null;
|
||||
try {
|
||||
c = resolver.query(EmailContent.Mailbox.CONTENT_URI,
|
||||
ACCOUNTID_TO_MAILBOXTYPE_PROJECTION,
|
||||
EmailContent.MailboxColumns.ACCOUNT_KEY + "=? AND " +
|
||||
EmailContent.MailboxColumns.TYPE + "=" + EmailContent.Mailbox.TYPE_TRASH,
|
||||
new String[] { Long.toString(accountId) }, null);
|
||||
if (c.moveToFirst()) {
|
||||
trashMailboxId = c.getLong(ACCOUNTID_TO_MAILBOXTYPE_COLUMN_ID);
|
||||
}
|
||||
} finally {
|
||||
if (c != null) c.close();
|
||||
}
|
||||
|
||||
// 3. If there's no trash mailbox, create one
|
||||
// TODO: Does this need to be signaled explicitly to the sync engines?
|
||||
if (trashMailboxId == -1) {
|
||||
Mailbox box = new Mailbox();
|
||||
|
||||
box.mDisplayName = mContext.getString(R.string.special_mailbox_name_trash);
|
||||
box.mAccountKey = accountId;
|
||||
box.mType = Mailbox.TYPE_TRASH;
|
||||
box.mSyncFrequency = EmailContent.Account.CHECK_INTERVAL_NEVER;
|
||||
box.mFlagVisible = true;
|
||||
box.saveOrUpdate(mProviderContext);
|
||||
trashMailboxId = box.mId;
|
||||
}
|
||||
|
||||
// 4. Change the mailbox key for the message we're "deleting"
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(EmailContent.MessageColumns.MAILBOX_KEY, trashMailboxId);
|
||||
Uri uri = ContentUris.withAppendedId(EmailContent.Message.SYNCED_CONTENT_URI, messageId);
|
||||
resolver.update(uri, cv, null, null);
|
||||
|
||||
// 5. Drop non-essential data for the message (e.g. attachments)
|
||||
// TODO: find the actual files (if any, if loaded) & delete them
|
||||
c = null;
|
||||
try {
|
||||
c = resolver.query(EmailContent.Attachment.CONTENT_URI,
|
||||
EmailContent.Attachment.CONTENT_PROJECTION,
|
||||
EmailContent.AttachmentColumns.MESSAGE_KEY + "=?",
|
||||
new String[] { Long.toString(messageId) }, null);
|
||||
while (c.moveToNext()) {
|
||||
// delete any associated storage
|
||||
// delete row?
|
||||
}
|
||||
} finally {
|
||||
if (c != null) c.close();
|
||||
}
|
||||
|
||||
// 6. For IMAP/POP3 we may need to kick off an immediate delete (depends on acct settings)
|
||||
// TODO write this
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple helper to determine if legacy MessagingController should be used
|
||||
*/
|
||||
|
|
|
@ -50,6 +50,7 @@ import android.widget.CursorAdapter;
|
|||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
|
||||
import java.util.Date;
|
||||
|
@ -201,7 +202,7 @@ public class MessageList extends ListActivity implements OnItemClickListener, On
|
|||
onOpenMessage(info.id);
|
||||
break;
|
||||
case R.id.delete:
|
||||
//onDelete(holder);
|
||||
onDelete(info.id);
|
||||
break;
|
||||
case R.id.reply:
|
||||
//onReply(holder);
|
||||
|
@ -265,6 +266,11 @@ public class MessageList extends ListActivity implements OnItemClickListener, On
|
|||
}
|
||||
}
|
||||
|
||||
private void onDelete(long messageId) {
|
||||
Controller.getInstance(getApplication()).deleteMessage(messageId, -1);
|
||||
Toast.makeText(this, R.string.message_deleted_toast, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
private void onToggleRead(long messageId, View itemView) {
|
||||
// TODO the read-unread state of the given message should be cached in the listview item.
|
||||
// Instead, we're going to pull it from the DB here (expensively and in the wrong thread)
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package com.android.email.activity;
|
||||
|
||||
import com.android.email.Controller;
|
||||
import com.android.email.Email;
|
||||
import com.android.email.MessagingController;
|
||||
import com.android.email.MessagingListener;
|
||||
|
@ -513,12 +514,9 @@ public class MessageView extends Activity
|
|||
}
|
||||
|
||||
private void onDelete() {
|
||||
if (mOldMessage != null) {
|
||||
MessagingController.getInstance(getApplication()).deleteMessage(
|
||||
mAccount,
|
||||
mFolder,
|
||||
mOldMessage,
|
||||
null);
|
||||
if (mMessage != null) {
|
||||
Controller.getInstance(getApplication()).deleteMessage(
|
||||
mMessageId, mMessage.mAccountKey);
|
||||
Toast.makeText(this, R.string.message_deleted_toast, Toast.LENGTH_SHORT).show();
|
||||
|
||||
if (onPrevious()) {
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.email;
|
||||
|
||||
import com.android.email.provider.EmailContent;
|
||||
import com.android.email.provider.EmailProvider;
|
||||
import com.android.email.provider.ProviderTestUtils;
|
||||
import com.android.email.provider.EmailContent.Account;
|
||||
import com.android.email.provider.EmailContent.Mailbox;
|
||||
import com.android.email.provider.EmailContent.Message;
|
||||
|
||||
import android.content.Context;
|
||||
import android.test.ProviderTestCase2;
|
||||
|
||||
/**
|
||||
* Tests of the Controller class that depend on the underlying provider.
|
||||
*
|
||||
* NOTE: It would probably make sense to rewrite this using a MockProvider, instead of the
|
||||
* ProviderTestCase (which is a real provider running on a temp database). This would be more of
|
||||
* a true "unit test".
|
||||
*
|
||||
* You can run this entire test case with:
|
||||
* runtest -c com.android.email.ControllerProviderOpsTests email
|
||||
*/
|
||||
public class ControllerProviderOpsTests extends ProviderTestCase2<EmailProvider> {
|
||||
|
||||
EmailProvider mProvider;
|
||||
Context mProviderContext;
|
||||
Context mContext;
|
||||
|
||||
public ControllerProviderOpsTests() {
|
||||
super(EmailProvider.class, EmailProvider.EMAIL_AUTHORITY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
mProviderContext = getMockContext();
|
||||
mContext = getContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tearDown() throws Exception {
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lightweight subclass of the Controller class allows injection of mock context
|
||||
*/
|
||||
public static class TestController extends Controller {
|
||||
|
||||
protected TestController(Context providerContext, Context systemContext) {
|
||||
super(systemContext);
|
||||
setProviderContext(providerContext);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the "delete message" function. Sunny day:
|
||||
* - message/mailbox/account all exist
|
||||
* - trash mailbox exists
|
||||
*/
|
||||
public void testDeleteMessage() {
|
||||
Account account1 = ProviderTestUtils.setupAccount("message-delete", true, mProviderContext);
|
||||
long account1Id = account1.mId;
|
||||
Mailbox box1 = ProviderTestUtils.setupMailbox("box1", account1Id, true, mProviderContext);
|
||||
long box1Id = box1.mId;
|
||||
Mailbox box2 = ProviderTestUtils.setupMailbox("box2", account1Id, false, mProviderContext);
|
||||
box2.mType = EmailContent.Mailbox.TYPE_TRASH;
|
||||
box2.save(mProviderContext);
|
||||
long box2Id = box2.mId;
|
||||
|
||||
Message message1 = ProviderTestUtils.setupMessage("message1", account1Id, box1Id, false,
|
||||
true, mProviderContext);
|
||||
long message1Id = message1.mId;
|
||||
|
||||
Controller ct = new TestController(mProviderContext, mContext);
|
||||
|
||||
ct.deleteMessage(message1Id, account1Id);
|
||||
|
||||
// now read back a fresh copy and confirm it's in the trash
|
||||
Message message1get = EmailContent.Message.restoreMessageWithId(mProviderContext,
|
||||
message1Id);
|
||||
assertEquals(box2Id, message1get.mMailboxKey);
|
||||
|
||||
// Now repeat test with accountId "unknown"
|
||||
Message message2 = ProviderTestUtils.setupMessage("message2", account1Id, box1Id, false,
|
||||
true, mProviderContext);
|
||||
long message2Id = message2.mId;
|
||||
|
||||
ct.deleteMessage(message2Id, -1);
|
||||
|
||||
// now read back a fresh copy and confirm it's in the trash
|
||||
Message message2get = EmailContent.Message.restoreMessageWithId(mProviderContext,
|
||||
message2Id);
|
||||
assertEquals(box2Id, message2get.mMailboxKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test deleting message when there is no trash mailbox
|
||||
*/
|
||||
public void testDeleteMessageNoTrash() {
|
||||
Account account1 =
|
||||
ProviderTestUtils.setupAccount("message-delete-notrash", true, mProviderContext);
|
||||
long account1Id = account1.mId;
|
||||
Mailbox box1 = ProviderTestUtils.setupMailbox("box1", account1Id, true, mProviderContext);
|
||||
long box1Id = box1.mId;
|
||||
|
||||
Message message1 =
|
||||
ProviderTestUtils.setupMessage("message1", account1Id, box1Id, false, true,
|
||||
mProviderContext);
|
||||
long message1Id = message1.mId;
|
||||
|
||||
Controller ct = new TestController(mProviderContext, mContext);
|
||||
|
||||
ct.deleteMessage(message1Id, account1Id);
|
||||
|
||||
// now read back a fresh copy and confirm it's in the trash
|
||||
Message message1get =
|
||||
EmailContent.Message.restoreMessageWithId(mProviderContext, message1Id);
|
||||
|
||||
// check the new mailbox and see if it looks right
|
||||
assertFalse(-1 == message1get.mMailboxKey);
|
||||
assertFalse(box1Id == message1get.mMailboxKey);
|
||||
Mailbox mailbox2get = EmailContent.Mailbox.restoreMailboxWithId(mProviderContext,
|
||||
message1get.mMailboxKey);
|
||||
assertEquals(EmailContent.Mailbox.TYPE_TRASH, mailbox2get.mType);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: releasing associated data (e.g. attachments, embedded images)
|
||||
*/
|
||||
}
|
Loading…
Reference in New Issue