diff --git a/src/com/fsck/k9/K9.java b/src/com/fsck/k9/K9.java
index 307bb4cae..b8cba28c0 100644
--- a/src/com/fsck/k9/K9.java
+++ b/src/com/fsck/k9/K9.java
@@ -1,6 +1,11 @@
package com.fsck.k9;
+import java.io.File;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
import android.app.Application;
import android.content.ComponentName;
import android.content.Context;
@@ -20,19 +25,42 @@ import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.BinaryTempFileBody;
-import com.fsck.k9.provider.MessageProvider;
import com.fsck.k9.service.BootReceiver;
import com.fsck.k9.service.MailService;
-import java.io.File;
-import java.lang.reflect.Method;
-
public class K9 extends Application
{
+ /**
+ * Components that are interested in knowing when the K9 instance is
+ * available and ready (Android invokes Application.onCreate() after other
+ * components') should implement this interface and register using
+ * {@link K9#registerApplicationAware(ApplicationAware)}.
+ */
+ public static interface ApplicationAware
+ {
+ /**
+ * Called when the Application instance is available and ready.
+ *
+ * @param application
+ * The application instance. Never null
.
+ * @throws Exception
+ */
+ void initializeComponent(K9 application) throws Exception;
+ }
+
public static Application app = null;
public static File tempDirectory;
public static final String LOG_TAG = "k9";
+ /**
+ * Components that are interested in knowing when the K9 instance is
+ * available and ready.
+ *
+ * @see ApplicationAware
+ */
+ private static List observers = new ArrayList();
+
+
public enum BACKGROUND_OPS
{
WHEN_CHECKED, ALWAYS, NEVER, WHEN_CHECKED_AUTO_SYNC
@@ -418,8 +446,6 @@ public class K9 extends Application
K9.setK9Language(sprefs.getString("language", ""));
K9.setK9Theme(sprefs.getInt("theme", android.R.style.Theme_Light));
MessagingController.getInstance(this).resetVisibleLimits(prefs.getAccounts());
- MessageProvider mp = new MessageProvider();
- mp.setApplication(this);
/*
* We have to give MimeMessage a temp directory because File.createTempFile(String, String)
@@ -487,9 +513,50 @@ public class K9 extends Application
broadcastIntent(K9.Intents.EmailReceived.ACTION_EMAIL_RECEIVED, account, folder, message);
}
+ @Override
+ public void searchStats(final AccountStats stats)
+ {
+ // let observers know a fetch occured
+ K9.this.sendBroadcast(new Intent(K9.Intents.EmailReceived.ACTION_REFRESH_OBSERVER, null));
+ }
});
+ notifyObservers();
+ }
+
+ /**
+ * since Android invokes Application.onCreate() only after invoking all
+ * other components' onCreate(), here is a way to notify interested
+ * component that the application is available and ready
+ */
+ protected void notifyObservers()
+ {
+ for (final ApplicationAware aware : observers)
+ {
+ try
+ {
+ aware.initializeComponent(this);
+ }
+ catch (Exception e)
+ {
+ Log.w(K9.LOG_TAG, "Failure when notifying " + aware, e);
+ }
+ }
+ }
+
+ /**
+ * Register a component to be notified when the {@link K9} instance is ready.
+ *
+ * @param component
+ * Never null
.
+ */
+ public static void registerApplicationAware(final ApplicationAware component)
+ {
+ if (!observers.contains(component))
+ {
+ observers.add(component);
+ }
}
public static String getK9Language()
diff --git a/src/com/fsck/k9/provider/MessageProvider.java b/src/com/fsck/k9/provider/MessageProvider.java
index e846211b4..75e2e4243 100644
--- a/src/com/fsck/k9/provider/MessageProvider.java
+++ b/src/com/fsck/k9/provider/MessageProvider.java
@@ -6,11 +6,8 @@ import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
-import android.app.Application;
import android.content.ContentProvider;
import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.MatrixCursor;
@@ -34,13 +31,212 @@ import com.fsck.k9.mail.store.LocalStore;
public class MessageProvider extends ContentProvider
{
+ protected interface QueryHandler
+ {
+ /**
+ * The path this instance is able to respond to.
+ *
+ * @return Never null
.
+ */
+ String getPath();
+
+ /**
+ * @param uri
+ * @param projection
+ * @param selection
+ * @param selectionArgs
+ * @param sortOrder
+ * @return
+ * @throws Exception
+ * @see {@link ContentProvider#query(Uri, String[], String, String[], String)}
+ */
+ Cursor query(Uri uri, String[] projection,
+ String selection, String[] selectionArgs, String sortOrder) throws Exception;
+ }
+
+ /**
+ * Retrieve messages from the integrated inbox.
+ */
+ protected class MessagesQueryHandler implements QueryHandler
+ {
+
+ @Override
+ public String getPath()
+ {
+ return "inbox_messages/";
+ }
+
+ @Override
+ public Cursor query(final Uri uri, final String[] projection, final String selection,
+ final String[] selectionArgs, final String sortOrder) throws Exception
+ {
+ return getMessages(projection);
+ }
+
+ /**
+ * @param projection
+ * Projection to use. If null
, use the default
+ * projection.
+ * @return Never null
.
+ * @throws InterruptedException
+ */
+ protected MatrixCursor getMessages(final String[] projection) throws InterruptedException
+ {
+ // TODO use the given projection if prevent
+ final MatrixCursor cursor = new MatrixCursor(DEFAULT_MESSAGE_PROJECTION);
+ final BlockingQueue> queue = new SynchronousQueue>();
+
+ // new code for integrated inbox, only execute this once as it will be processed afterwards via the listener
+ final SearchAccount integratedInboxAccount = new SearchAccount(getContext(), true, null, null);
+ final MessagingController msgController = MessagingController.getInstance(K9.app);
+
+ msgController.searchLocalMessages(integratedInboxAccount, null,
+ new MesssageInfoHolderRetrieverListener(queue));
+
+ final List holders = queue.take();
+
+ // TODO add sort order parameter
+ Collections.sort(holders, new MessageList.ReverseComparator(
+ new MessageList.DateComparator()));
+
+ int id = -1;
+ for (final MessageInfoHolder holder : holders)
+ {
+ final Message message = holder.message;
+ id++;
+
+ cursor.addRow(new Object[]
+ {
+ id,
+ holder.fullDate,
+ holder.sender,
+ holder.subject,
+ holder.preview,
+ holder.account,
+ holder.uri,
+ CONTENT_URI + "/delete_message/"
+ + message.getFolder().getAccount().getAccountNumber() + "/"
+ + message.getFolder().getName() + "/" + message.getUid() });
+ }
+ return cursor;
+ }
+
+ }
+
+ /**
+ * Retrieve the account list.
+ */
+ protected class AccountsQueryHandler implements QueryHandler
+ {
+
+ @Override
+ public String getPath()
+ {
+ return "accounts";
+ }
+
+ @Override
+ public Cursor query(final Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) throws Exception
+ {
+ return getAllAccounts();
+ }
+
+ public Cursor getAllAccounts()
+ {
+ String[] projection = new String[] { "accountNumber", "accountName" };
+
+ MatrixCursor ret = new MatrixCursor(projection);
+
+ for (Account account : Preferences.getPreferences(getContext()).getAccounts())
+ {
+ Object[] values = new Object[2];
+ values[0] = account.getAccountNumber();
+ values[1] = account.getDescription();
+ ret.addRow(values);
+ }
+
+ return ret;
+ }
+
+ }
+
+ /**
+ * Retrieve the unread message count for a given account specified by its
+ * {@link Account#getAccountNumber() number}.
+ */
+ protected class UnreadQueryHandler implements QueryHandler
+ {
+
+ @Override
+ public String getPath()
+ {
+ return "account_unread/#";
+ }
+
+ @Override
+ public Cursor query(final Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) throws Exception
+ {
+ List segments = null;
+ int accountId = -1;
+ segments = uri.getPathSegments();
+ accountId = Integer.parseInt(segments.get(1));
+ return getAccountUnread(accountId);
+ }
+
+ public Cursor getAccountUnread(int accountNumber)
+ {
+ String[] projection = new String[] { "accountName", "unread" };
+
+ MatrixCursor ret = new MatrixCursor(projection);
+
+ Account myAccount;
+ AccountStats myAccountStats = null;
+
+ Object[] values = new Object[2];
+
+ for (Account account : Preferences.getPreferences(getContext()).getAccounts())
+ {
+ if (account.getAccountNumber()==accountNumber)
+ {
+ myAccount = account;
+ try
+ {
+ myAccountStats = account.getStats(getContext());
+ values[0] = myAccount.getDescription();
+ values[1] = myAccountStats.unreadMessageCount;
+ ret.addRow(values);
+ }
+ catch (MessagingException e)
+ {
+ Log.e(K9.LOG_TAG, e.getMessage());
+ values[0] = "Unknown";
+ values[1] = 0;
+ }
+ }
+ }
+
+ return ret;
+ }
+ }
+
+ /**
+ * Synchronized listener used to retrieve {@link MessageInfoHolder}s using a
+ * given {@link BlockingQueue}.
+ */
protected class MesssageInfoHolderRetrieverListener extends MessagingListener
{
private final BlockingQueue> queue;
private List holders = new ArrayList();
- private MesssageInfoHolderRetrieverListener(BlockingQueue> queue)
+ /**
+ * @param queue
+ * Never null
. The synchronized channel to use
+ * to retrieve {@link MessageInfoHolder}s.
+ */
+ public MesssageInfoHolderRetrieverListener(BlockingQueue> queue)
{
this.queue = queue;
}
@@ -51,7 +247,7 @@ public class MessageProvider extends ContentProvider
{
for (final Message message : messages)
{
- holders.add(new MessageInfoHolder(context, message));
+ holders.add(new MessageInfoHolder(getContext(), message));
}
}
@@ -73,27 +269,7 @@ public class MessageProvider extends ContentProvider
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
- private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
-
- private static final int URI_INBOX_MESSAGES = 0;
- private static final int URI_DELETE_MESSAGE = 1;
- private static final int URI_ACCOUNTS = 2;
- private static final int URI_ACCOUNT_UNREAD = 3;
-
- private static Context context = null;
- private static boolean mIsListenerRegister = false;
-
- private static Application mApp;
-
- static
- {
- URI_MATCHER.addURI(AUTHORITY, "inbox_messages/", URI_INBOX_MESSAGES);
- URI_MATCHER.addURI(AUTHORITY, "delete_message/", URI_DELETE_MESSAGE);
- URI_MATCHER.addURI(AUTHORITY, "accounts", URI_ACCOUNTS);
- URI_MATCHER.addURI(AUTHORITY, "account_unread/#", URI_ACCOUNT_UNREAD);
- }
-
- static String[] messages_projection = new String[]
+ private static final String[] DEFAULT_MESSAGE_PROJECTION = new String[]
{
"id",
"date",
@@ -104,115 +280,35 @@ public class MessageProvider extends ContentProvider
"uri",
"delUri"
};
- MessagingListener mListener = new MessagingListener()
- {
- public void searchStats(AccountStats stats)
- {
- notifyDatabaseModification();
- }
- };
+ /**
+ * URI matcher used for
+ * {@link #query(Uri, String[], String, String[], String)}
+ */
+ private UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
- public Cursor getAllAccounts()
- {
- String[] projection = new String[] { "accountNumber", "accountName" };
-
- MatrixCursor ret = new MatrixCursor(projection);
-
- for (Account account : Preferences.getPreferences(getContext()).getAccounts())
- {
- Object[] values = new Object[2];
- values[0] = account.getAccountNumber();
- values[1] = account.getDescription();
- ret.addRow(values);
- }
-
- return ret;
- }
-
- public Cursor getAccountUnread(int accountNumber)
- {
- String[] projection = new String[] { "accountName", "unread" };
-
- MatrixCursor ret = new MatrixCursor(projection);
-
- Account myAccount;
- AccountStats myAccountStats = null;
-
- Object[] values = new Object[2];
-
- for (Account account : Preferences.getPreferences(getContext()).getAccounts())
- {
- if (account.getAccountNumber()==accountNumber)
- {
- myAccount = account;
- try
- {
- myAccountStats = account.getStats(getContext());
- values[0] = myAccount.getDescription();
- values[1] = myAccountStats.unreadMessageCount;
- ret.addRow(values);
- }
- catch (MessagingException e)
- {
- Log.e(K9.LOG_TAG, e.getMessage());
- values[0] = "Unknown";
- values[1] = 0;
- }
- }
- }
-
- return ret;
- }
-
- public void setApplication(Application app)
- {
- if (context == null)
- {
- context = app.getApplicationContext();
- }
- if (app != null)
- {
- mApp = app;
- MessagingController msgController = MessagingController.getInstance(mApp);
- if ((msgController != null) && (!mIsListenerRegister))
- {
- msgController.addListener(mListener);
- mIsListenerRegister = true;
- }
- }
-
- }
+ /**
+ * Handlers registered to respond to
+ * {@link #query(Uri, String[], String, String[], String)}
+ */
+ private List mQueryHandlers = new ArrayList();
@Override
public boolean onCreate()
{
- context = getContext();
+ registerQueryHandler(new AccountsQueryHandler());
+ registerQueryHandler(new MessagesQueryHandler());
+ registerQueryHandler(new UnreadQueryHandler());
- if (mApp != null)
- {
- MessagingController msgController = MessagingController.getInstance(mApp);
- if ((msgController != null) && (!mIsListenerRegister))
- {
- msgController.addListener(mListener);
- mIsListenerRegister = true;
- }
- }
-
- return false;
+ return true;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs)
{
-
if (K9.DEBUG)
- Log.d(K9.LOG_TAG, "delete");
-
- if (mApp == null)
{
- Log.d(K9.LOG_TAG, "K9 not ready");
- return 0;
+ Log.v(K9.LOG_TAG, "delete");
}
// Nota : can only delete a message
@@ -241,21 +337,23 @@ public class MessageProvider extends ContentProvider
Message msg = null;
try
{
- Folder lf = LocalStore.getLocalInstance(myAccount, mApp).getFolder(folderName);
+ Folder lf = LocalStore.getLocalInstance(myAccount, K9.app).getFolder(folderName);
int msgCount = lf.getMessageCount();
if (K9.DEBUG)
+ {
Log.d(K9.LOG_TAG, "folder msg count = " + msgCount);
+ }
msg = lf.getMessage(msgUid);
}
catch (MessagingException e)
{
- Log.e(K9.LOG_TAG, e.getMessage());
+ Log.e(K9.LOG_TAG, "Unable to retrieve message", e);
}
// launch command to delete the message
if ((myAccount != null) && (msg != null))
{
- MessagingController.getInstance(mApp).deleteMessages(new Message[] { msg }, mListener);
+ MessagingController.getInstance(K9.app).deleteMessages(new Message[] { msg }, null);
}
return 0;
@@ -274,86 +372,29 @@ public class MessageProvider extends ContentProvider
}
@Override
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
+ public Cursor query(final Uri uri, final String[] projection, final String selection,
+ final String[] selectionArgs, final String sortOrder)
{
+ final Cursor cursor;
- if (K9.DEBUG)
- Log.d(K9.LOG_TAG, "query");
+ final int code = mUriMatcher.match(uri);
- if (mApp == null)
+ if (code == -1)
{
- Log.d(K9.LOG_TAG, "K9 not ready");
- return null;
+ throw new IllegalStateException("Unrecognized URI: " + uri);
}
- Cursor cursor;
- switch (URI_MATCHER.match(uri))
+ try
{
- case URI_INBOX_MESSAGES:
- {
- final MatrixCursor mCursor = new MatrixCursor(messages_projection);
- final BlockingQueue> queue = new SynchronousQueue>();
-
- // new code for integrated inbox, only execute this once as it will be processed afterwards via the listener
- SearchAccount integratedInboxAccount = new SearchAccount(getContext(), true, null, null);
- MessagingController msgController = MessagingController.getInstance(mApp);
-
- msgController.searchLocalMessages(integratedInboxAccount, null,
- new MesssageInfoHolderRetrieverListener(queue));
-
- List holders;
-
- try
- {
- holders = queue.take();
- }
- catch (InterruptedException e)
- {
- Log.e(K9.LOG_TAG, "Unable to retrieve message list", e);
- return null;
- }
-
- Collections.sort(holders, new MessageList.ReverseComparator(new MessageList.DateComparator()));
-
- int id = -1;
- for (final MessageInfoHolder holder : holders)
- {
- final Message message = holder.message;
- id++;
-
- mCursor.addRow(new Object[]
- {
- id,
- holder.fullDate,
- holder.sender,
- holder.subject,
- holder.preview,
- holder.account,
- holder.uri,
- CONTENT_URI + "/delete_message/"
- + message.getFolder().getAccount().getAccountNumber() + "/"
- + message.getFolder().getName() + "/" + message.getUid() });
- }
-
- cursor = mCursor;
- break;
- }
-
- case URI_ACCOUNTS:
- cursor = getAllAccounts();
- break;
-
- case URI_ACCOUNT_UNREAD:
-
- List segments = null;
- int accountId = -1;
- segments = uri.getPathSegments();
- accountId = Integer.parseInt(segments.get(1));
- cursor = getAccountUnread(accountId);
- break;
-
- default:
- throw new IllegalStateException("Unrecognized URI:" + uri);
+ // since we used the list index as the UriMatcher code, using it
+ // back to retrieve the handler from the list
+ final QueryHandler handler = mQueryHandlers.get(code);
+ cursor = handler.query(uri, projection, selection, selectionArgs, sortOrder);
+ }
+ catch (Exception e)
+ {
+ Log.e(K9.LOG_TAG, "Unable to execute query for URI: " + uri, e);
+ return null;
}
return cursor;
@@ -364,24 +405,33 @@ public class MessageProvider extends ContentProvider
{
if (K9.DEBUG)
- Log.d(K9.LOG_TAG, "update");
+ {
+ Log.v(K9.LOG_TAG, "update");
+ }
//TBD
return 0;
}
- public static void notifyDatabaseModification()
+ /**
+ * Register a {@link QueryHandler} to handle a certain {@link Uri} for
+ * {@link #query(Uri, String[], String, String[], String)}
+ *
+ * @param handler
+ * Never null
.
+ */
+ protected void registerQueryHandler(final QueryHandler handler)
{
+ if (mQueryHandlers.contains(handler))
+ {
+ return;
+ }
+ mQueryHandlers.add(handler);
- if (K9.DEBUG)
- Log.d(K9.LOG_TAG, "notifyDatabaseModification -> UPDATE");
-
- Intent intent = new Intent(K9.Intents.EmailReceived.ACTION_REFRESH_OBSERVER, null);
- context.sendBroadcast(intent);
-
- context.getContentResolver().notifyChange(CONTENT_URI, null);
-
+ // use the index inside the list as the UriMatcher code for that handler
+ final int code = mQueryHandlers.indexOf(handler);
+ mUriMatcher.addURI(AUTHORITY, handler.getPath(), code);
}
}