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:
parent
d9907c7641
commit
ec15f2356e
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue