Extract the mailbox lookup logic into an independent class

This new class MailboxFinder is responsible for looking for a mailbox
by an account id and a mailbox type.

If a mailbox is not found on the first try, it'll tell Controller
to refresh the mailbox list, and try again later.

This will be used by MessageListXL.

Change-Id: I4adc3db025fb271c254aa2b58b3b753281dc7398
This commit is contained in:
Makoto Onuki 2010-07-27 17:41:19 -07:00
parent d9907c7641
commit ec15f2356e
4 changed files with 733 additions and 158 deletions

View File

@ -105,6 +105,15 @@ public class Controller {
return sInstance;
}
/**
* Inject a mock controller. Used only for testing. Affects future calls to getInstance().
*
* Tests that use this method MUST clean it up by calling this method again with null.
*/
public synchronized static void injectMockControllerForTest(Controller mockController) {
sInstance = mockController;
}
/**
* For testing only: Inject a different context for provider access. This will be
* used internally for access the underlying provider (e.g. getContentResolver().query()).
@ -246,29 +255,27 @@ public class Controller {
/**
* Request a remote update of mailboxes for an account.
*
* TODO: Clean up threading in MessagingController cases (or perhaps here in Controller)
*/
public void updateMailboxList(final long accountId) {
IEmailService service = getServiceForAccount(accountId);
if (service != null) {
// Service implementation
try {
service.updateFolderList(accountId);
} catch (RemoteException e) {
// TODO Change exception handling to be consistent with however this method
// is implemented for other protocols
Log.d("updateMailboxList", "RemoteException" + e);
}
} else {
// MessagingController implementation
Utility.runAsync(new Runnable() {
public void run() {
Utility.runAsync(new Runnable() {
@Override
public void run() {
final IEmailService service = getServiceForAccount(accountId);
if (service != null) {
// Service implementation
try {
service.updateFolderList(accountId);
} catch (RemoteException e) {
// TODO Change exception handling to be consistent with however this method
// is implemented for other protocols
Log.d("updateMailboxList", "RemoteException" + e);
}
} else {
// MessagingController implementation
mLegacyController.listFolders(accountId, mLegacyListener);
}
});
}
}
});
}
/**

View File

@ -0,0 +1,213 @@
/*
* Copyright (C) 2010 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.activity;
import com.android.email.Controller;
import com.android.email.ControllerResultUiThreadWrapper;
import com.android.email.Email;
import com.android.email.Utility;
import com.android.email.mail.MessagingException;
import com.android.email.provider.EmailContent.Account;
import com.android.email.provider.EmailContent.AccountColumns;
import com.android.email.provider.EmailContent.Mailbox;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Handler;
import android.util.Log;
/**
* A class that finds a mailbox ID by account ID and mailbox type.
*
* If an account doesn't have a mailbox of a specified type, it refreshes the mailbox list and
* try looking for again.
*/
public class MailboxFinder {
private final Context mContext;
private final Controller mController;
private final Controller.Result mControllerResults;
private final long mAccountId;
private final int mMailboxType;
private final Callback mCallback;
private FindMailboxTask mTask;
/**
* Callback for results.
*/
public interface Callback {
public void onAccountNotFound();
public void onMailboxNotFound(long accountId);
public void onAccountSecurityHold(long accountId);
public void onMailboxFound(long accountId, long mailboxId);
}
/**
* Creates an instance for {@code accountId} and {@code mailboxType}. (But won't start yet)
*
* Must be called on the UI thread.
*/
public MailboxFinder(Context context, long accountId, int mailboxType, Callback callback) {
if (accountId == -1) {
throw new UnsupportedOperationException();
}
mContext = context.getApplicationContext();
mController = Controller.getInstance(context);
mAccountId = accountId;
mMailboxType = mailboxType;
mCallback = callback;
mControllerResults = new ControllerResultUiThreadWrapper<ControllerResults>(
new Handler(), new ControllerResults());
mController.addResultCallback(mControllerResults);
}
/**
* Start looking up.
*
* Must be called on the UI thread.
*/
public void startLookup() {
stop();
mTask = new FindMailboxTask(true);
mTask.execute();
}
/**
* Stop the running worker task, if exists.
*/
public void stop() {
Utility.cancelTaskInterrupt(mTask);
mTask = null;
}
/**
* Stop the running task, if exists, and clean up internal resources. (MUST be called.)
*/
public void close() {
mController.removeResultCallback(mControllerResults);
stop();
}
private class ControllerResults extends Controller.Result {
@Override
public void updateMailboxListCallback(MessagingException result, long accountId,
int progress) {
if (result != null) {
// Error while updating the mailbox list. Notify the UI...
mCallback.onMailboxNotFound(mAccountId);
} else {
// Messagebox list updated, look for mailbox again...
if (progress == 100 && accountId == mAccountId) {
mTask = new FindMailboxTask(false);
mTask.execute();
}
}
}
}
/**
* Async task for finding a single mailbox by type. If a mailbox of a type is not found,
* and {@code okToRecurse} is true, we update the mailbox list and try looking again.
*/
private class FindMailboxTask extends AsyncTask<Void, Void, Long> {
private final boolean mOkToRecurse;
private static final int RESULT_MAILBOX_FOUND = 0;
private static final int RESULT_ACCOUNT_SECURITY_HOLD = 1;
private static final int RESULT_ACCOUNT_NOT_FOUND = 2;
private static final int RESULT_MAILBOX_NOT_FOUND = 3;
private static final int RESULT_START_NETWORK_LOOK_UP = 4;
private int mResult = -1;
/**
* Special constructor to cache some local info
*/
public FindMailboxTask(boolean okToRecurse) {
mOkToRecurse = okToRecurse;
}
@Override
protected Long doInBackground(Void... params) {
// Quick check that account is not in security hold
if (Account.isSecurityHold(mContext, mAccountId)) {
mResult = RESULT_ACCOUNT_SECURITY_HOLD;
return Mailbox.NO_MAILBOX;
}
// See if we can find the requested mailbox in the DB.
long mailboxId = Mailbox.findMailboxOfType(mContext, mAccountId, mMailboxType);
if (mailboxId != Mailbox.NO_MAILBOX) {
mResult = RESULT_MAILBOX_FOUND;
return mailboxId; // Found
}
// Mailbox not found. Does the account really exists?
final boolean accountExists = Account.isValidId(mContext, mAccountId);
if (accountExists) {
if (mOkToRecurse) {
// launch network lookup
mResult = RESULT_START_NETWORK_LOOK_UP;
} else {
mResult = RESULT_MAILBOX_NOT_FOUND;
}
} else {
mResult = RESULT_ACCOUNT_NOT_FOUND;
}
return Mailbox.NO_MAILBOX;
}
@Override
protected void onPostExecute(Long mailboxId) {
Log.w(Email.LOG_TAG, "" + isCancelled() + " " + mResult);
if (isCancelled()) {
return;
}
switch (mResult) {
case RESULT_ACCOUNT_SECURITY_HOLD:
Log.w(Email.LOG_TAG, "Account security hold.");
mCallback.onAccountSecurityHold(mAccountId);
return;
case RESULT_ACCOUNT_NOT_FOUND:
Log.w(Email.LOG_TAG, "Account not found.");
mCallback.onAccountNotFound();
return;
case RESULT_MAILBOX_NOT_FOUND:
Log.w(Email.LOG_TAG, "Mailbox not found.");
mCallback.onMailboxNotFound(mAccountId);
return;
case RESULT_START_NETWORK_LOOK_UP:
// Not found locally. Let's sync the mailbox list...
Log.i(Email.LOG_TAG, "Mailbox not found locally. Starting network lookup.");
mController.updateMailboxList(mAccountId);
return;
case RESULT_MAILBOX_FOUND:
mCallback.onMailboxFound(mAccountId, mailboxId);
return;
default:
throw new RuntimeException();
}
}
}
/* package */ Controller.Result getControllerResultsForTest() {
return mControllerResults;
}
}

View File

@ -36,7 +36,6 @@ import com.android.email.service.MailService;
import android.app.Activity;
import android.app.NotificationManager;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
@ -49,8 +48,8 @@ import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
@ -80,9 +79,11 @@ public class MessageList extends Activity implements OnClickListener,
// DB access
private ContentResolver mResolver;
private FindMailboxTask mFindMailboxTask;
private SetTitleTask mSetTitleTask;
private MailboxFinder mMailboxFinder;
private MailboxFinderCallback mMailboxFinderCallback = new MailboxFinderCallback();
private static final int MAILBOX_NAME_COLUMN_ID = 0;
private static final int MAILBOX_NAME_COLUMN_ACCOUNT_KEY = 1;
private static final int MAILBOX_NAME_COLUMN_TYPE = 2;
@ -212,19 +213,16 @@ public class MessageList extends Activity implements OnClickListener,
// TODO Possible ANR. getAccountIdFromShortcutSafeUri accesses DB.
long accountId = (uri == null) ? -1
: Account.getAccountIdFromShortcutSafeUri(this, uri);
// TODO Both branches have almost identical code. Unify them.
// (And why not "okay to recurse" in the first case?)
if (accountId != -1) {
// A content URI was provided - try to look up the account
mFindMailboxTask = new FindMailboxTask(accountId, mailboxType, false);
mFindMailboxTask.execute();
} else {
// Go by account id + type
if (accountId == -1) {
accountId = intent.getLongExtra(EXTRA_ACCOUNT_ID, -1);
mFindMailboxTask = new FindMailboxTask(accountId, mailboxType, true);
mFindMailboxTask.execute();
}
if (accountId == -1) {
launchWelcomeAndFinish();
return;
}
mMailboxFinder = new MailboxFinder(this, accountId, mailboxType,
mMailboxFinderCallback);
mMailboxFinder.startLookup();
}
// TODO set title to "account > mailbox (#unread)"
}
@ -257,12 +255,20 @@ public class MessageList extends Activity implements OnClickListener,
protected void onDestroy() {
super.onDestroy();
Utility.cancelTaskInterrupt(mFindMailboxTask);
mFindMailboxTask = null;
if (mMailboxFinder != null) {
mMailboxFinder.close();
mMailboxFinder = null;
}
Utility.cancelTaskInterrupt(mSetTitleTask);
mSetTitleTask = null;
}
private void launchWelcomeAndFinish() {
Welcome.actionStart(this);
finish();
}
/**
* Called when the list fragment can't find mailbox/account.
*/
@ -430,96 +436,6 @@ public class MessageList extends Activity implements OnClickListener,
}
}
/**
* Async task for finding a single mailbox by type (possibly even going to the network).
*
* This is much too complex, as implemented. It uses this AsyncTask to check for a mailbox,
* then (if not found) a Controller call to refresh mailboxes from the server, and a handler
* to relaunch this task (a 2nd time) to read the results of the network refresh. The core
* problem is that we have two different non-UI-thread jobs (reading DB and reading network)
* and two different paradigms for dealing with them. Some unification would be needed here
* to make this cleaner.
*
* TODO: If this problem spreads to other operations, find a cleaner way to handle it.
*/
private class FindMailboxTask extends AsyncTask<Void, Void, Long> {
private final long mAccountId;
private final int mMailboxType;
private final boolean mOkToRecurse;
private static final int ACTION_DEFAULT = 0;
private static final int SHOW_WELCOME_ACTIVITY = 1;
private static final int SHOW_SECURITY_ACTIVITY = 2;
private static final int START_NETWORK_LOOK_UP = 3;
private int mAction = ACTION_DEFAULT;
/**
* Special constructor to cache some local info
*/
public FindMailboxTask(long accountId, int mailboxType, boolean okToRecurse) {
mAccountId = accountId;
mMailboxType = mailboxType;
mOkToRecurse = okToRecurse;
}
@Override
protected Long doInBackground(Void... params) {
// Quick check that account is not in security hold
if (mAccountId != -1 && Account.isSecurityHold(MessageList.this, mAccountId)) {
mAction = SHOW_SECURITY_ACTIVITY;
return Mailbox.NO_MAILBOX;
}
// See if we can find the requested mailbox in the DB.
long mailboxId = Mailbox.findMailboxOfType(MessageList.this, mAccountId, mMailboxType);
if (mailboxId == Mailbox.NO_MAILBOX) {
// Mailbox not found. Does the account really exists?
final boolean accountExists = Account.isValidId(MessageList.this, mAccountId);
if (accountExists && mOkToRecurse) {
// launch network lookup
mAction = START_NETWORK_LOOK_UP;
} else {
// We don't want to do the network lookup, or the account doesn't exist in the
// first place.
mAction = SHOW_WELCOME_ACTIVITY;
}
}
return mailboxId;
}
@Override
protected void onPostExecute(Long mailboxId) {
switch (mAction) {
case SHOW_SECURITY_ACTIVITY:
// launch the security setup activity
Intent i = AccountSecurity.actionUpdateSecurityIntent(
MessageList.this, mAccountId);
MessageList.this.startActivityForResult(i, REQUEST_SECURITY);
return;
case SHOW_WELCOME_ACTIVITY:
// Let the Welcome activity show the default screen.
Welcome.actionStart(MessageList.this);
finish();
return;
case START_NETWORK_LOOK_UP:
mControllerCallback.getWrappee().presetMailboxListCallback(
mMailboxType, mAccountId);
// TODO updateMailboxList accessed DB, so we shouldn't call on the UI thread,
// but we should fix the Controller side. (Other Controller methods too access
// DB but are called on the UI thread.)
mController.updateMailboxList(mAccountId);
return;
default:
// At this point, mailboxId != NO_MAILBOX
mSetTitleTask = new SetTitleTask(mailboxId);
mSetTitleTask.execute();
mListFragment.openMailbox(mAccountId, mailboxId);
return;
}
}
}
/**
* Handle the eventual result from the security update activity
*
@ -631,14 +547,6 @@ public class MessageList extends Activity implements OnClickListener,
mListFragment.showProgressIcon(show);
}
private void lookupMailboxType(long accountId, int mailboxType) {
// kill running async task, if any
Utility.cancelTaskInterrupt(mFindMailboxTask);
// start new one. do not recurse back to controller.
mFindMailboxTask = new FindMailboxTask(accountId, mailboxType, false);
mFindMailboxTask.execute();
}
private void showErrorBanner(String message) {
boolean isVisible = mErrorBanner.getVisibility() == View.VISIBLE;
if (message != null) {
@ -666,31 +574,7 @@ public class MessageList extends Activity implements OnClickListener,
private class ControllerResults extends Controller.Result {
// This is used to alter the connection banner operation for sending messages
MessagingException mSendMessageException;
// These values are set by FindMailboxTask.
private int mWaitForMailboxType = -1;
private long mWaitForMailboxAccount = -1;
public void presetMailboxListCallback(int mailboxType, long accountId) {
mWaitForMailboxType = mailboxType;
mWaitForMailboxAccount = accountId;
}
@Override
public void updateMailboxListCallback(MessagingException result,
long accountKey, int progress) {
// updateMailboxList is never the end goal in MessageList, so we don't show
// these errors. There are a couple of corner cases that we miss reporting, but
// this is better than reporting a number of non-problem intermediate states.
// updateBanner(result, progress, mMailboxId);
updateProgress(result, progress);
if (progress == 100 && accountKey == mWaitForMailboxAccount) {
mWaitForMailboxAccount = -1;
lookupMailboxType(accountKey, mWaitForMailboxType);
}
}
private MessagingException mSendMessageException;
// TODO check accountKey and only react to relevant notifications
@Override
@ -782,4 +666,33 @@ public class MessageList extends Activity implements OnClickListener,
}
}
}
private class MailboxFinderCallback implements MailboxFinder.Callback {
@Override
public void onMailboxFound(long accountId, long mailboxId) {
mSetTitleTask = new SetTitleTask(mailboxId);
mSetTitleTask.execute();
mListFragment.openMailbox(accountId, mailboxId);
}
@Override
public void onAccountNotFound() {
// Let the Welcome activity show the default screen.
launchWelcomeAndFinish();
}
@Override
public void onMailboxNotFound(long accountId) {
// Let the Welcome activity show the default screen.
launchWelcomeAndFinish();
}
@Override
public void onAccountSecurityHold(long accountId) {
// launch the security setup activity
Intent i = AccountSecurity.actionUpdateSecurityIntent(
MessageList.this, accountId);
MessageList.this.startActivityForResult(i, REQUEST_SECURITY);
}
}
}

View File

@ -0,0 +1,442 @@
/*
* Copyright (C) 2010 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.activity;
import com.android.email.Controller;
import com.android.email.Email;
import com.android.email.TestUtils;
import com.android.email.mail.MessagingException;
import com.android.email.provider.EmailContent;
import com.android.email.provider.EmailContent.Account;
import com.android.email.provider.EmailContent.Mailbox;
import com.android.email.provider.EmailProvider;
import com.android.email.provider.ProviderTestUtils;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.test.InstrumentationTestCase;
import android.test.IsolatedContext;
import android.test.ProviderTestCase2;
import android.test.RenamingDelegatingContext;
import android.test.mock.MockContentResolver;
import android.test.mock.MockContext;
import android.test.suitebuilder.annotation.LargeTest;
import java.io.File;
/**
* Test case for {@link MailboxFinder}.
*
* We need to use {@link InstrumentationTestCase} so that we can create AsyncTasks on the UI thread
* using {@link InstrumentationTestCase#runTestOnUiThread}. This class also needs an isolated
* context, which is provided by {@link ProviderTestCase2}. We can't derive from two classes,
* so we just copy the code for an isolate context to here.
*/
@LargeTest
public class MailboxFinderTest extends InstrumentationTestCase {
private static final int TIMEOUT = 10; // in seconds
// These are needed to run the provider in a separate context
private IsolatedContext mProviderContext;
private MockContentResolver mResolver;
private EmailProvider mProvider;
// Test target
private MailboxFinder mMailboxFinder;
// Mock to track callback invocations.
private MockController mMockController;
private MockCallback mCallback;
private Context getContext() {
return getInstrumentation().getTargetContext();
}
@Override
protected void setUp() throws Exception {
super.setUp();
setUpProviderContext();
mCallback = new MockCallback();
mMockController = new MockController(getContext());
Controller.injectMockControllerForTest(mMockController);
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
Controller.injectMockControllerForTest(null);
}
/** Copied from ProviderTestCase2 and modified a bit. */
private class MockContext2 extends MockContext {
@Override
public Resources getResources() {
return getContext().getResources();
}
@Override
public File getDir(String name, int mode) {
return getContext().getDir("mockcontext2_" + name, mode);
}
}
/** {@link IsolatedContext} + getApplicationContext() */
private static class MyIsolatedContext extends IsolatedContext {
public MyIsolatedContext(ContentResolver resolver, Context targetContext) {
super(resolver, targetContext);
}
@Override
public Context getApplicationContext() {
return this;
}
}
/** Copied from ProviderTestCase2 and modified a bit. */
private void setUpProviderContext() {
mResolver = new MockContentResolver();
final String filenamePrefix = "test.";
RenamingDelegatingContext targetContextWrapper = new
RenamingDelegatingContext(
new MockContext2(), // The context that most methods are
//delegated to
getContext(), // The context that file methods are delegated to
filenamePrefix);
mProviderContext = new MyIsolatedContext(mResolver, targetContextWrapper);
mProviderContext.getContentResolver();
mProvider = new EmailProvider();
mProvider.attachInfo(mProviderContext, null);
assertNotNull(mProvider);
mResolver.addProvider(EmailContent.AUTHORITY, mProvider);
}
/**
* Create an account and returns the ID.
*/
private long createAccount(boolean securityHold) {
Account acct = ProviderTestUtils.setupAccount("acct1", false, mProviderContext);
if (securityHold) {
acct.mFlags |= Account.FLAGS_SECURITY_HOLD;
}
acct.save(mProviderContext);
return acct.mId;
}
/**
* Create a mailbox and return the ID.
*/
private long createMailbox(long accountId, int mailboxType) {
EmailContent.Mailbox box = new EmailContent.Mailbox();
box.mDisplayName = "mailbox";
box.mAccountKey = accountId;
box.mType = mailboxType;
box.mFlagVisible = true;
box.mVisibleLimit = Email.VISIBLE_LIMIT_DEFAULT;
box.save(mProviderContext);
return box.mId;
}
/**
* Create a {@link MailboxFinder} and kick it.
*/
private void createAndStartFinder(final long accountId, final int mailboxType)
throws Throwable {
runTestOnUiThread(new Runnable() {
@Override
public void run() {
mMailboxFinder = new MailboxFinder(mProviderContext, accountId, mailboxType,
mCallback);
mMailboxFinder.startLookup();
}
});
}
/**
* Wait until any of the {@link MailboxFinder.Callback} method or
* {@link Controller#updateMailboxList} is called.
*/
private void waitUntilCallbackCalled() {
TestUtils.waitUntil("", new TestUtils.Condition() {
@Override
public boolean isMet() {
return mCallback.isAnyMethodCalled() || mMockController.mCalledUpdateMailboxList;
}
}, TIMEOUT);
}
/**
* Test: Account is on security hold.
*/
public void testSecurityHold() throws Throwable {
final long accountId = createAccount(true);
createAndStartFinder(accountId, Mailbox.TYPE_INBOX);
waitUntilCallbackCalled();
assertFalse(mCallback.mCalledAccountNotFound);
assertTrue(mCallback.mCalledAccountSecurityHold);
assertFalse(mCallback.mCalledMailboxFound);
assertFalse(mCallback.mCalledMailboxNotFound);
assertFalse(mMockController.mCalledUpdateMailboxList);
}
/**
* Test: Account does not exist.
*/
public void testAccountNotFound() throws Throwable {
createAndStartFinder(123456, Mailbox.TYPE_INBOX); // No such account.
waitUntilCallbackCalled();
assertTrue(mCallback.mCalledAccountNotFound);
assertFalse(mCallback.mCalledAccountSecurityHold);
assertFalse(mCallback.mCalledMailboxFound);
assertFalse(mCallback.mCalledMailboxNotFound);
assertFalse(mMockController.mCalledUpdateMailboxList);
}
/**
* Test: Mailbox found
*/
public void testMailboxFound() throws Throwable {
final long accountId = createAccount(false);
final long mailboxId = createMailbox(accountId, Mailbox.TYPE_INBOX);
createAndStartFinder(accountId, Mailbox.TYPE_INBOX);
waitUntilCallbackCalled();
assertFalse(mCallback.mCalledAccountNotFound);
assertFalse(mCallback.mCalledAccountSecurityHold);
assertTrue(mCallback.mCalledMailboxFound);
assertFalse(mCallback.mCalledMailboxNotFound);
assertFalse(mMockController.mCalledUpdateMailboxList);
assertEquals(accountId, mCallback.mAccountId);
assertEquals(mailboxId, mCallback.mMailboxId);
}
/**
* Test: Account exists, but mailbox doesn't -> Get {@link Controller} to update the mailbox
* list -> mailbox still doesn't exist.
*/
public void testMailboxNotFound() throws Throwable {
final long accountId = createAccount(false);
createAndStartFinder(accountId, Mailbox.TYPE_INBOX);
waitUntilCallbackCalled();
// Mailbox not found, so the finder try network-looking up.
assertFalse(mCallback.mCalledAccountNotFound);
assertFalse(mCallback.mCalledAccountSecurityHold);
assertFalse(mCallback.mCalledMailboxFound);
assertFalse(mCallback.mCalledMailboxNotFound);
// Controller.updateMailboxList() should have been called, with the account id.
assertTrue(mMockController.mCalledUpdateMailboxList);
assertEquals(accountId, mMockController.mPassedAccountId);
mMockController.reset();
// Imitate the mCallback...
runTestOnUiThread(new Runnable() {
@Override
public void run() {
mMailboxFinder.getControllerResultsForTest().updateMailboxListCallback(
null, accountId, 100);
}
});
// Task should have started, so wait for the response...
waitUntilCallbackCalled();
assertFalse(mCallback.mCalledAccountNotFound);
assertFalse(mCallback.mCalledAccountSecurityHold);
assertFalse(mCallback.mCalledMailboxFound);
assertTrue(mCallback.mCalledMailboxNotFound);
assertFalse(mMockController.mCalledUpdateMailboxList);
}
/**
* Test: Account exists, but mailbox doesn't -> Get {@link Controller} to update the mailbox
* list -> found mailbox this time.
*/
public void testMailboxFoundOnNetwork() throws Throwable {
final long accountId = createAccount(false);
createAndStartFinder(accountId, Mailbox.TYPE_INBOX);
waitUntilCallbackCalled();
// Mailbox not found, so the finder try network-looking up.
assertFalse(mCallback.mCalledAccountNotFound);
assertFalse(mCallback.mCalledAccountSecurityHold);
assertFalse(mCallback.mCalledMailboxFound);
assertFalse(mCallback.mCalledMailboxNotFound);
// Controller.updateMailboxList() should have been called, with the account id.
assertTrue(mMockController.mCalledUpdateMailboxList);
assertEquals(accountId, mMockController.mPassedAccountId);
mMockController.reset();
// Create mailbox at this point.
final long mailboxId = createMailbox(accountId, Mailbox.TYPE_INBOX);
// Imitate the mCallback...
runTestOnUiThread(new Runnable() {
@Override
public void run() {
mMailboxFinder.getControllerResultsForTest().updateMailboxListCallback(
null, accountId, 100);
}
});
// Task should have started, so wait for the response...
waitUntilCallbackCalled();
assertFalse(mCallback.mCalledAccountNotFound);
assertFalse(mCallback.mCalledAccountSecurityHold);
assertTrue(mCallback.mCalledMailboxFound);
assertFalse(mCallback.mCalledMailboxNotFound);
assertFalse(mMockController.mCalledUpdateMailboxList);
assertEquals(accountId, mCallback.mAccountId);
assertEquals(mailboxId, mCallback.mMailboxId);
}
/**
* Test: Account exists, but mailbox doesn't -> Get {@link Controller} to update the mailbox
* list -> network error.
*/
public void testMailboxNotFoundNetworkError() throws Throwable {
final long accountId = createAccount(false);
createAndStartFinder(accountId, Mailbox.TYPE_INBOX);
waitUntilCallbackCalled();
// Mailbox not found, so the finder try network-looking up.
assertFalse(mCallback.mCalledAccountNotFound);
assertFalse(mCallback.mCalledAccountSecurityHold);
assertFalse(mCallback.mCalledMailboxFound);
assertFalse(mCallback.mCalledMailboxNotFound);
// Controller.updateMailboxList() should have been called, with the account id.
assertTrue(mMockController.mCalledUpdateMailboxList);
assertEquals(accountId, mMockController.mPassedAccountId);
mMockController.reset();
// Imitate the mCallback...
runTestOnUiThread(new Runnable() {
@Override
public void run() {
// network error.
mMailboxFinder.getControllerResultsForTest().updateMailboxListCallback(
new MessagingException("Network error"), accountId, 0);
}
});
assertFalse(mCallback.mCalledAccountNotFound);
assertFalse(mCallback.mCalledAccountSecurityHold);
assertFalse(mCallback.mCalledMailboxFound);
assertTrue(mCallback.mCalledMailboxNotFound);
assertFalse(mMockController.mCalledUpdateMailboxList);
}
/**
* Test: Mailbox not found (mailbox of different type exists)
*/
public void testMailboxNotFound2() throws Throwable {
final long accountId = createAccount(false);
final long mailboxId = createMailbox(accountId, Mailbox.TYPE_DRAFTS);
createAndStartFinder(accountId, Mailbox.TYPE_INBOX);
waitUntilCallbackCalled();
assertFalse(mCallback.mCalledAccountNotFound);
assertFalse(mCallback.mCalledAccountSecurityHold);
assertFalse(mCallback.mCalledMailboxFound);
assertFalse(mCallback.mCalledMailboxNotFound);
assertTrue(mMockController.mCalledUpdateMailboxList);
}
/**
* A {@link Controller} that remembers if updateMailboxList has been called.
*/
private static class MockController extends Controller {
public long mPassedAccountId;
public boolean mCalledUpdateMailboxList;
public void reset() {
mPassedAccountId = -1;
mCalledUpdateMailboxList = false;
}
protected MockController(Context context) {
super(context);
}
@Override
public void updateMailboxList(long accountId) {
mCalledUpdateMailboxList = true;
mPassedAccountId = accountId;
}
}
/**
* Callback that logs what method is called with what arguments.
*/
private static class MockCallback implements MailboxFinder.Callback {
public boolean mCalledAccountNotFound;
public boolean mCalledAccountSecurityHold;
public boolean mCalledMailboxFound;
public boolean mCalledMailboxNotFound;
public long mAccountId = -1;
public long mMailboxId = -1;
public boolean isAnyMethodCalled() {
return mCalledAccountNotFound || mCalledAccountSecurityHold || mCalledMailboxFound
|| mCalledMailboxNotFound;
}
@Override
public void onAccountNotFound() {
mCalledAccountNotFound = true;
}
@Override
public void onAccountSecurityHold(long accountId) {
mCalledAccountSecurityHold = true;
mAccountId = accountId;
}
@Override
public void onMailboxFound(long accountId, long mailboxId) {
mCalledMailboxFound = true;
mAccountId = accountId;
mMailboxId = mailboxId;
}
@Override
public void onMailboxNotFound(long accountId) {
mCalledMailboxNotFound = true;
mAccountId = accountId;
}
}
}