Merge "Refactoring widget part 2"
This commit is contained in:
commit
fc8a65aecf
|
@ -17,28 +17,23 @@
|
||||||
package com.android.email.widget;
|
package com.android.email.widget;
|
||||||
|
|
||||||
import com.android.email.Email;
|
import com.android.email.Email;
|
||||||
import com.android.email.UiUtilities;
|
|
||||||
import com.android.email.R;
|
import com.android.email.R;
|
||||||
import com.android.email.ResourceHelper;
|
import com.android.email.ResourceHelper;
|
||||||
|
import com.android.email.UiUtilities;
|
||||||
import com.android.email.activity.MessageCompose;
|
import com.android.email.activity.MessageCompose;
|
||||||
import com.android.email.activity.Welcome;
|
import com.android.email.activity.Welcome;
|
||||||
import com.android.email.data.ThrottlingCursorLoader;
|
|
||||||
import com.android.email.provider.WidgetProvider.WidgetService;
|
import com.android.email.provider.WidgetProvider.WidgetService;
|
||||||
import com.android.emailcommon.provider.EmailContent;
|
|
||||||
import com.android.emailcommon.provider.EmailContent.Account;
|
|
||||||
import com.android.emailcommon.provider.EmailContent.AccountColumns;
|
|
||||||
import com.android.emailcommon.provider.EmailContent.Mailbox;
|
import com.android.emailcommon.provider.EmailContent.Mailbox;
|
||||||
import com.android.emailcommon.provider.EmailContent.Message;
|
import com.android.emailcommon.provider.EmailContent.Message;
|
||||||
import com.android.emailcommon.provider.EmailContent.MessageColumns;
|
|
||||||
import com.android.emailcommon.utility.Utility;
|
import com.android.emailcommon.utility.Utility;
|
||||||
|
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.appwidget.AppWidgetManager;
|
import android.appwidget.AppWidgetManager;
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.ContentUris;
|
import android.content.ContentUris;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.Loader;
|
import android.content.Loader;
|
||||||
|
import android.content.Loader.OnLoadCompleteListener;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
|
@ -59,11 +54,17 @@ import android.widget.RemoteViews;
|
||||||
import android.widget.RemoteViewsService;
|
import android.widget.RemoteViewsService;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
|
|
||||||
import junit.framework.Assert;
|
/**
|
||||||
|
* The email widget.
|
||||||
public class EmailWidget implements RemoteViewsService.RemoteViewsFactory {
|
*
|
||||||
|
* Threading notes:
|
||||||
|
* - All methods must be called on the UI thread, except for {@link WidgetUpdater#doInBackground}.
|
||||||
|
* - {@link WidgetUpdater#doInBackground} must not read/write any members of {@link EmailWidget}.
|
||||||
|
* - (So no synchronizations are required in this class)
|
||||||
|
*/
|
||||||
|
public class EmailWidget implements RemoteViewsService.RemoteViewsFactory,
|
||||||
|
OnLoadCompleteListener<Cursor> {
|
||||||
public static final String TAG = "EmailWidget";
|
public static final String TAG = "EmailWidget";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -96,16 +97,8 @@ public class EmailWidget implements RemoteViewsService.RemoteViewsFactory {
|
||||||
private static final Uri COMMAND_URI_VIEW_MESSAGE =
|
private static final Uri COMMAND_URI_VIEW_MESSAGE =
|
||||||
COMMAND_URI.buildUpon().appendPath(COMMAND_NAME_VIEW_MESSAGE).build();
|
COMMAND_URI.buildUpon().appendPath(COMMAND_NAME_VIEW_MESSAGE).build();
|
||||||
|
|
||||||
|
|
||||||
private static final int TOTAL_COUNT_UNKNOWN = -1;
|
|
||||||
private static final int MAX_MESSAGE_LIST_COUNT = 25;
|
private static final int MAX_MESSAGE_LIST_COUNT = 25;
|
||||||
|
|
||||||
private static final String SORT_TIMESTAMP_DESCENDING = MessageColumns.TIMESTAMP + " DESC";
|
|
||||||
private static final String SORT_ID_ASCENDING = AccountColumns.ID + " ASC";
|
|
||||||
private static final String[] ID_NAME_PROJECTION = {Account.RECORD_ID, Account.DISPLAY_NAME};
|
|
||||||
private static final int ID_NAME_COLUMN_ID = 0;
|
|
||||||
private static final int ID_NAME_COLUMN_NAME = 1;
|
|
||||||
|
|
||||||
private static String sSubjectSnippetDivider;
|
private static String sSubjectSnippetDivider;
|
||||||
private static String sConfigureText;
|
private static String sConfigureText;
|
||||||
private static int sSenderFontSize;
|
private static int sSenderFontSize;
|
||||||
|
@ -115,48 +108,25 @@ public class EmailWidget implements RemoteViewsService.RemoteViewsFactory {
|
||||||
private static int sLightTextColor;
|
private static int sLightTextColor;
|
||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private final ContentResolver mResolver;
|
|
||||||
private final AppWidgetManager mWidgetManager;
|
private final AppWidgetManager mWidgetManager;
|
||||||
|
|
||||||
// The widget identifier
|
// The widget identifier
|
||||||
private final int mWidgetId;
|
private final int mWidgetId;
|
||||||
|
|
||||||
// The cursor underlying the message list for this widget; this must only be modified while
|
|
||||||
// holding mCursorLock
|
|
||||||
private volatile Cursor mCursor;
|
|
||||||
// A lock on our cursor, which is used in the UI thread while inflating views, and by
|
|
||||||
// our Loader in the background
|
|
||||||
private final Object mCursorLock = new Object();
|
|
||||||
// Number of records in the cursor
|
|
||||||
private int mCursorCount = TOTAL_COUNT_UNKNOWN;
|
|
||||||
// The widget's loader (derived from ThrottlingCursorLoader)
|
// The widget's loader (derived from ThrottlingCursorLoader)
|
||||||
private ViewCursorLoader mLoader;
|
private final EmailWidgetLoader mLoader;
|
||||||
private final ResourceHelper mResourceHelper;
|
private final ResourceHelper mResourceHelper;
|
||||||
// Number of defined accounts
|
|
||||||
private int mAccountCount = TOTAL_COUNT_UNKNOWN;
|
|
||||||
|
|
||||||
// The current view type (all mail, unread, or starred for now)
|
/**
|
||||||
/*package*/ ViewType mViewType = ViewType.STARRED;
|
* The cursor for the messages, with some extra info such as the number of accounts.
|
||||||
|
*
|
||||||
|
* Note this cursor can be closed any time by the loader. Always use {@link #isCursorValid()}
|
||||||
|
* before touching its contents.
|
||||||
|
*/
|
||||||
|
private EmailWidgetLoader.CursorWithCounts mCursor;
|
||||||
|
|
||||||
// The projection to be used by the WidgetLoader
|
/** The current view type */
|
||||||
private static final String[] WIDGET_PROJECTION = new String[] {
|
/* package */ WidgetView mWidgetView = WidgetView.UNINITIALIZED_VIEW;
|
||||||
EmailContent.RECORD_ID, MessageColumns.DISPLAY_NAME, MessageColumns.TIMESTAMP,
|
|
||||||
MessageColumns.SUBJECT, MessageColumns.FLAG_READ, MessageColumns.FLAG_FAVORITE,
|
|
||||||
MessageColumns.FLAG_ATTACHMENT, MessageColumns.MAILBOX_KEY, MessageColumns.SNIPPET,
|
|
||||||
MessageColumns.ACCOUNT_KEY, MessageColumns.FLAGS
|
|
||||||
};
|
|
||||||
private static final int WIDGET_COLUMN_ID = 0;
|
|
||||||
private static final int WIDGET_COLUMN_DISPLAY_NAME = 1;
|
|
||||||
private static final int WIDGET_COLUMN_TIMESTAMP = 2;
|
|
||||||
private static final int WIDGET_COLUMN_SUBJECT = 3;
|
|
||||||
private static final int WIDGET_COLUMN_FLAG_READ = 4;
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
private static final int WIDGET_COLUMN_FLAG_FAVORITE = 5;
|
|
||||||
private static final int WIDGET_COLUMN_FLAG_ATTACHMENT = 6;
|
|
||||||
private static final int WIDGET_COLUMN_MAILBOX_KEY = 7;
|
|
||||||
private static final int WIDGET_COLUMN_SNIPPET = 8;
|
|
||||||
private static final int WIDGET_COLUMN_ACCOUNT_KEY = 9;
|
|
||||||
private static final int WIDGET_COLUMN_FLAGS = 10;
|
|
||||||
|
|
||||||
public EmailWidget(Context context, int _widgetId) {
|
public EmailWidget(Context context, int _widgetId) {
|
||||||
super();
|
super();
|
||||||
|
@ -164,11 +134,11 @@ public class EmailWidget implements RemoteViewsService.RemoteViewsFactory {
|
||||||
Log.d(TAG, "Creating EmailWidget with id = " + _widgetId);
|
Log.d(TAG, "Creating EmailWidget with id = " + _widgetId);
|
||||||
}
|
}
|
||||||
mContext = context.getApplicationContext();
|
mContext = context.getApplicationContext();
|
||||||
mResolver = mContext.getContentResolver();
|
|
||||||
mWidgetManager = AppWidgetManager.getInstance(mContext);
|
mWidgetManager = AppWidgetManager.getInstance(mContext);
|
||||||
|
|
||||||
mWidgetId = _widgetId;
|
mWidgetId = _widgetId;
|
||||||
mLoader = new ViewCursorLoader();
|
mLoader = new EmailWidgetLoader(mContext);
|
||||||
|
mLoader.registerListener(0, this);
|
||||||
if (sSubjectSnippetDivider == null) {
|
if (sSubjectSnippetDivider == null) {
|
||||||
// Initialize string, color, dimension resources
|
// Initialize string, color, dimension resources
|
||||||
Resources res = mContext.getResources();
|
Resources res = mContext.getResources();
|
||||||
|
@ -185,169 +155,38 @@ public class EmailWidget implements RemoteViewsService.RemoteViewsFactory {
|
||||||
mResourceHelper = ResourceHelper.getInstance(mContext);
|
mResourceHelper = ResourceHelper.getInstance(mContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateWidget(boolean validateView) {
|
public void start() {
|
||||||
new WidgetUpdateTask().execute(validateView);
|
// The default view is UNINITIALIZED_VIEW, and we switch to the next one, which should
|
||||||
|
// be the initial view. (the first view shown to the user.)
|
||||||
|
switchView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isCursorValid() {
|
||||||
|
return mCursor != null && !mCursor.isClosed();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Task for updating widget data (eg: the header, view list items, etc...)
|
* Called when the loader finished loading data. Update the widget.
|
||||||
* If parameter to {@link #execute(Boolean...)} is <code>true</code>, the current
|
|
||||||
* view is validated against the current set of accounts. And if the current view
|
|
||||||
* is determined to be invalid, the view will automatically progress to the next
|
|
||||||
* valid view.
|
|
||||||
*/
|
*/
|
||||||
private final class WidgetUpdateTask extends AsyncTask<Boolean, Void, Boolean> {
|
@Override
|
||||||
@Override
|
public void onLoadComplete(Loader<Cursor> loader, Cursor cursor) {
|
||||||
protected Boolean doInBackground(Boolean... validateView) {
|
// Save away the cursor
|
||||||
mAccountCount = EmailContent.count(mContext, EmailContent.Account.CONTENT_URI);
|
mCursor = (EmailWidgetLoader.CursorWithCounts) cursor;
|
||||||
// If displaying invalid view, switch to the next view
|
mWidgetView = mLoader.getLoadingWidgetView();
|
||||||
return !validateView[0] || isViewValid();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
RemoteViews views = new RemoteViews(mContext.getPackageName(), R.layout.widget);
|
||||||
protected void onPostExecute(Boolean isValidView) {
|
updateHeader();
|
||||||
updateHeader();
|
setupTitleAndCount(views);
|
||||||
if (!isValidView) {
|
mWidgetManager.partiallyUpdateAppWidget(mWidgetId, views);
|
||||||
switchView();
|
mWidgetManager.notifyAppWidgetViewDataChanged(mWidgetId, R.id.message_list);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ThrottlingCursorLoader does all of the heavy lifting in managing the data loading
|
* Start loading the data. At this point nothing on the widget changes -- the current view
|
||||||
* task; all we need is to register a listener so that we're notified when the load is
|
* will remain valid until the loader loads the latest data.
|
||||||
* complete.
|
|
||||||
*/
|
*/
|
||||||
private final class ViewCursorLoader extends ThrottlingCursorLoader {
|
private void loadView(WidgetView view) {
|
||||||
protected ViewCursorLoader() {
|
mLoader.load(view);
|
||||||
super(mContext, Message.CONTENT_URI, WIDGET_PROJECTION, mViewType.selection,
|
|
||||||
mViewType.selectionArgs, SORT_TIMESTAMP_DESCENDING);
|
|
||||||
registerListener(0, new OnLoadCompleteListener<Cursor>() {
|
|
||||||
@Override
|
|
||||||
public void onLoadComplete(Loader<Cursor> loader, Cursor cursor) {
|
|
||||||
synchronized (mCursorLock) {
|
|
||||||
// Save away the cursor
|
|
||||||
mCursor = cursor;
|
|
||||||
// Reset the notification Uri to our Message table notifier URI
|
|
||||||
mCursor.setNotificationUri(mResolver, Message.NOTIFIER_URI);
|
|
||||||
// Save away the count (for display)
|
|
||||||
mCursorCount = mCursor.getCount();
|
|
||||||
if (Email.DEBUG) {
|
|
||||||
Log.d(TAG, "onLoadComplete, count = " + cursor.getCount());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RemoteViews views =
|
|
||||||
new RemoteViews(mContext.getPackageName(), R.layout.widget);
|
|
||||||
setupTitleAndCount(views);
|
|
||||||
mWidgetManager.partiallyUpdateAppWidget(mWidgetId, views);
|
|
||||||
mWidgetManager.notifyAppWidgetViewDataChanged(mWidgetId, R.id.message_list);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stop any pending load, reset selection parameters, and start loading
|
|
||||||
* Must be called from the UI thread
|
|
||||||
* @param viewType the current ViewType
|
|
||||||
*/
|
|
||||||
private void load(ViewType viewType) {
|
|
||||||
reset();
|
|
||||||
setSelection(viewType.selection);
|
|
||||||
setSelectionArgs(viewType.selectionArgs);
|
|
||||||
startLoading();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize to first appropriate view (depending on the number of accounts)
|
|
||||||
*/
|
|
||||||
public void init() {
|
|
||||||
// Just update the account count & header; no need to validate the view
|
|
||||||
updateWidget(false);
|
|
||||||
switchView(); // TODO Do we really need this??
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset cursor and cursor count, notify widget that list data is invalid, and start loading
|
|
||||||
* with our current ViewType
|
|
||||||
*/
|
|
||||||
private void loadView() {
|
|
||||||
synchronized(mCursorLock) {
|
|
||||||
mCursorCount = TOTAL_COUNT_UNKNOWN;
|
|
||||||
mCursor = null;
|
|
||||||
mWidgetManager.notifyAppWidgetViewDataChanged(mWidgetId, R.id.message_list);
|
|
||||||
mLoader.load(mViewType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Switch to the next widget view (all -> account1 -> ... -> account n -> unread -> starred)
|
|
||||||
*
|
|
||||||
* This must be called on a background thread. Use {@link #switchView} on the UI thread.
|
|
||||||
*/
|
|
||||||
private synchronized void switchToNextView() {
|
|
||||||
switch(mViewType) {
|
|
||||||
// If we're in starred and there is more than one account, go to "all mail"
|
|
||||||
// Otherwise, fall through to the accounts themselves
|
|
||||||
case STARRED:
|
|
||||||
if (EmailContent.count(mContext, Account.CONTENT_URI) > 1) {
|
|
||||||
mViewType = ViewType.ALL_INBOX;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
//$FALL-THROUGH$
|
|
||||||
case ALL_INBOX:
|
|
||||||
ViewType.ACCOUNT.selectionArgs[0] = "0";
|
|
||||||
//$FALL-THROUGH$
|
|
||||||
case ACCOUNT:
|
|
||||||
// Find the next account (or, if none, default to UNREAD)
|
|
||||||
String idString = ViewType.ACCOUNT.selectionArgs[0];
|
|
||||||
Cursor c = mResolver.query(Account.CONTENT_URI, ID_NAME_PROJECTION, "_id>?",
|
|
||||||
new String[] {idString}, SORT_ID_ASCENDING);
|
|
||||||
try {
|
|
||||||
if (c.moveToFirst()) {
|
|
||||||
mViewType = ViewType.ACCOUNT;
|
|
||||||
mViewType.selectionArgs[0] = c.getString(ID_NAME_COLUMN_ID);
|
|
||||||
mViewType.setTitle(c.getString(ID_NAME_COLUMN_NAME));
|
|
||||||
} else {
|
|
||||||
mViewType = ViewType.UNREAD;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
c.close();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case UNREAD:
|
|
||||||
mViewType = ViewType.STARRED;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether the current view is valid. The following rules determine if a view is
|
|
||||||
* considered valid:
|
|
||||||
* 1. If the view is either {@link ViewType#STARRED} or {@link ViewType#UNREAD}, always
|
|
||||||
* returns <code>true</code>.
|
|
||||||
* 2. If the view is {@link ViewType#ALL_INBOX}, returns <code>true</code> if more than
|
|
||||||
* one account is defined. Otherwise, returns <code>false</code>.
|
|
||||||
* 3. If the view is {@link ViewType#ACCOUNT}, returns <code>true</code> if the account
|
|
||||||
* is defined. Otherwise, returns <code>false</code>.
|
|
||||||
*/
|
|
||||||
private boolean isViewValid() {
|
|
||||||
switch(mViewType) {
|
|
||||||
case ALL_INBOX:
|
|
||||||
// "all inbox" is valid only if there is more than one account
|
|
||||||
return (EmailContent.count(mContext, Account.CONTENT_URI) > 1);
|
|
||||||
case ACCOUNT:
|
|
||||||
// Ensure current account still exists
|
|
||||||
String idString = ViewType.ACCOUNT.selectionArgs[0];
|
|
||||||
Cursor c = mResolver.query(Account.CONTENT_URI, ID_NAME_PROJECTION, "_id=?",
|
|
||||||
new String[] {idString}, SORT_ID_ASCENDING);
|
|
||||||
try {
|
|
||||||
return c.moveToFirst();
|
|
||||||
} finally {
|
|
||||||
c.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -451,11 +290,11 @@ public class EmailWidget implements RemoteViewsService.RemoteViewsFactory {
|
||||||
|
|
||||||
private void setupTitleAndCount(RemoteViews views) {
|
private void setupTitleAndCount(RemoteViews views) {
|
||||||
// Set up the title (view type + count of messages)
|
// Set up the title (view type + count of messages)
|
||||||
views.setTextViewText(R.id.widget_title, mViewType.getTitle(mContext));
|
views.setTextViewText(R.id.widget_title, mWidgetView.getTitle(mContext));
|
||||||
views.setTextViewText(R.id.widget_tap, sConfigureText);
|
views.setTextViewText(R.id.widget_tap, sConfigureText);
|
||||||
String count = "";
|
String count = "";
|
||||||
if (mCursorCount != TOTAL_COUNT_UNKNOWN) {
|
if (isCursorValid()) {
|
||||||
count = UiUtilities.getMessageCountForUi(mContext, mCursor.getCount(), false);
|
count = UiUtilities.getMessageCountForUi(mContext, mCursor.getMessageCount(), false);
|
||||||
}
|
}
|
||||||
views.setTextViewText(R.id.widget_count, count);
|
views.setTextViewText(R.id.widget_count, count);
|
||||||
}
|
}
|
||||||
|
@ -463,7 +302,7 @@ public class EmailWidget implements RemoteViewsService.RemoteViewsFactory {
|
||||||
* Update the "header" of the widget (i.e. everything that doesn't include the scrolling
|
* Update the "header" of the widget (i.e. everything that doesn't include the scrolling
|
||||||
* message list)
|
* message list)
|
||||||
*/
|
*/
|
||||||
public void updateHeader() {
|
private void updateHeader() {
|
||||||
if (Email.DEBUG) {
|
if (Email.DEBUG) {
|
||||||
Log.d(TAG, "updateWidget " + mWidgetId);
|
Log.d(TAG, "updateWidget " + mWidgetId);
|
||||||
}
|
}
|
||||||
|
@ -480,7 +319,7 @@ public class EmailWidget implements RemoteViewsService.RemoteViewsFactory {
|
||||||
|
|
||||||
setupTitleAndCount(views);
|
setupTitleAndCount(views);
|
||||||
|
|
||||||
if (mAccountCount == 0) {
|
if (!isCursorValid() || mCursor.getAccountCount() == 0) {
|
||||||
// Hide compose icon & show "touch to configure" text
|
// Hide compose icon & show "touch to configure" text
|
||||||
views.setViewVisibility(R.id.widget_compose, View.INVISIBLE);
|
views.setViewVisibility(R.id.widget_compose, View.INVISIBLE);
|
||||||
views.setViewVisibility(R.id.message_list, View.GONE);
|
views.setViewVisibility(R.id.message_list, View.GONE);
|
||||||
|
@ -566,79 +405,78 @@ public class EmailWidget implements RemoteViewsService.RemoteViewsFactory {
|
||||||
@Override
|
@Override
|
||||||
public RemoteViews getViewAt(int position) {
|
public RemoteViews getViewAt(int position) {
|
||||||
// Use the cursor to set up the widget
|
// Use the cursor to set up the widget
|
||||||
synchronized (mCursorLock) {
|
if (!isCursorValid() || !mCursor.moveToPosition(position)) {
|
||||||
if (mCursor == null || mCursor.isClosed() || !mCursor.moveToPosition(position)) {
|
return getLoadingView();
|
||||||
return getLoadingView();
|
|
||||||
}
|
|
||||||
RemoteViews views =
|
|
||||||
new RemoteViews(mContext.getPackageName(), R.layout.widget_list_item);
|
|
||||||
boolean isUnread = mCursor.getInt(WIDGET_COLUMN_FLAG_READ) != 1;
|
|
||||||
int drawableId = R.drawable.widget_read_conversation_selector;
|
|
||||||
if (isUnread) {
|
|
||||||
drawableId = R.drawable.widget_unread_conversation_selector;
|
|
||||||
}
|
|
||||||
views.setInt(R.id.widget_message, "setBackgroundResource", drawableId);
|
|
||||||
|
|
||||||
// Add style to sender
|
|
||||||
SpannableStringBuilder from =
|
|
||||||
new SpannableStringBuilder(mCursor.getString(WIDGET_COLUMN_DISPLAY_NAME));
|
|
||||||
from.setSpan(
|
|
||||||
isUnread ? new StyleSpan(Typeface.BOLD) : new StyleSpan(Typeface.NORMAL), 0,
|
|
||||||
from.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
CharSequence styledFrom = addStyle(from, sSenderFontSize, sDefaultTextColor);
|
|
||||||
views.setTextViewText(R.id.widget_from, styledFrom);
|
|
||||||
|
|
||||||
long timestamp = mCursor.getLong(WIDGET_COLUMN_TIMESTAMP);
|
|
||||||
// Get a nicely formatted date string (relative to today)
|
|
||||||
String date = DateUtils.getRelativeTimeSpanString(mContext, timestamp).toString();
|
|
||||||
// Add style to date
|
|
||||||
CharSequence styledDate = addStyle(date, sDateFontSize, sDefaultTextColor);
|
|
||||||
views.setTextViewText(R.id.widget_date, styledDate);
|
|
||||||
|
|
||||||
// Add style to subject/snippet
|
|
||||||
String subject = mCursor.getString(WIDGET_COLUMN_SUBJECT);
|
|
||||||
String snippet = mCursor.getString(WIDGET_COLUMN_SNIPPET);
|
|
||||||
CharSequence subjectAndSnippet =
|
|
||||||
getStyledSubjectSnippet(subject, snippet, !isUnread);
|
|
||||||
views.setTextViewText(R.id.widget_subject, subjectAndSnippet);
|
|
||||||
|
|
||||||
int messageFlags = mCursor.getInt(WIDGET_COLUMN_FLAGS);
|
|
||||||
boolean hasInvite = (messageFlags & Message.FLAG_INCOMING_MEETING_INVITE) != 0;
|
|
||||||
views.setViewVisibility(R.id.widget_invite, hasInvite ? View.VISIBLE : View.GONE);
|
|
||||||
|
|
||||||
boolean hasAttachment = mCursor.getInt(WIDGET_COLUMN_FLAG_ATTACHMENT) != 0;
|
|
||||||
views.setViewVisibility(R.id.widget_attachment,
|
|
||||||
hasAttachment ? View.VISIBLE : View.GONE);
|
|
||||||
|
|
||||||
if (mViewType == ViewType.ACCOUNT) {
|
|
||||||
views.setViewVisibility(R.id.color_chip, View.INVISIBLE);
|
|
||||||
} else {
|
|
||||||
long accountId = mCursor.getLong(WIDGET_COLUMN_ACCOUNT_KEY);
|
|
||||||
int colorId = mResourceHelper.getAccountColorId(accountId);
|
|
||||||
// Don't show the chip if we have 1 or fewer accounts
|
|
||||||
if (mAccountCount > 1 && colorId != ResourceHelper.UNDEFINED_RESOURCE_ID) {
|
|
||||||
// Color defined by resource ID, so, use it
|
|
||||||
views.setViewVisibility(R.id.color_chip, View.VISIBLE);
|
|
||||||
views.setImageViewResource(R.id.color_chip, colorId);
|
|
||||||
} else {
|
|
||||||
// Color not defined by resource ID, nothing we can do, so, hide the chip
|
|
||||||
views.setViewVisibility(R.id.color_chip, View.INVISIBLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set button intents for view, reply, and delete
|
|
||||||
String messageId = mCursor.getString(WIDGET_COLUMN_ID);
|
|
||||||
String mailboxId = mCursor.getString(WIDGET_COLUMN_MAILBOX_KEY);
|
|
||||||
setFillInIntent(views, R.id.widget_message, COMMAND_URI_VIEW_MESSAGE,
|
|
||||||
messageId, mailboxId);
|
|
||||||
|
|
||||||
return views;
|
|
||||||
}
|
}
|
||||||
|
RemoteViews views =
|
||||||
|
new RemoteViews(mContext.getPackageName(), R.layout.widget_list_item);
|
||||||
|
boolean isUnread = mCursor.getInt(EmailWidgetLoader.WIDGET_COLUMN_FLAG_READ) != 1;
|
||||||
|
int drawableId = R.drawable.widget_read_conversation_selector;
|
||||||
|
if (isUnread) {
|
||||||
|
drawableId = R.drawable.widget_unread_conversation_selector;
|
||||||
|
}
|
||||||
|
views.setInt(R.id.widget_message, "setBackgroundResource", drawableId);
|
||||||
|
|
||||||
|
// Add style to sender
|
||||||
|
SpannableStringBuilder from =
|
||||||
|
new SpannableStringBuilder(mCursor.getString(
|
||||||
|
EmailWidgetLoader.WIDGET_COLUMN_DISPLAY_NAME));
|
||||||
|
from.setSpan(
|
||||||
|
isUnread ? new StyleSpan(Typeface.BOLD) : new StyleSpan(Typeface.NORMAL), 0,
|
||||||
|
from.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
CharSequence styledFrom = addStyle(from, sSenderFontSize, sDefaultTextColor);
|
||||||
|
views.setTextViewText(R.id.widget_from, styledFrom);
|
||||||
|
|
||||||
|
long timestamp = mCursor.getLong(EmailWidgetLoader.WIDGET_COLUMN_TIMESTAMP);
|
||||||
|
// Get a nicely formatted date string (relative to today)
|
||||||
|
String date = DateUtils.getRelativeTimeSpanString(mContext, timestamp).toString();
|
||||||
|
// Add style to date
|
||||||
|
CharSequence styledDate = addStyle(date, sDateFontSize, sDefaultTextColor);
|
||||||
|
views.setTextViewText(R.id.widget_date, styledDate);
|
||||||
|
|
||||||
|
// Add style to subject/snippet
|
||||||
|
String subject = mCursor.getString(EmailWidgetLoader.WIDGET_COLUMN_SUBJECT);
|
||||||
|
String snippet = mCursor.getString(EmailWidgetLoader.WIDGET_COLUMN_SNIPPET);
|
||||||
|
CharSequence subjectAndSnippet =
|
||||||
|
getStyledSubjectSnippet(subject, snippet, !isUnread);
|
||||||
|
views.setTextViewText(R.id.widget_subject, subjectAndSnippet);
|
||||||
|
|
||||||
|
int messageFlags = mCursor.getInt(EmailWidgetLoader.WIDGET_COLUMN_FLAGS);
|
||||||
|
boolean hasInvite = (messageFlags & Message.FLAG_INCOMING_MEETING_INVITE) != 0;
|
||||||
|
views.setViewVisibility(R.id.widget_invite, hasInvite ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
|
boolean hasAttachment =
|
||||||
|
mCursor.getInt(EmailWidgetLoader.WIDGET_COLUMN_FLAG_ATTACHMENT) != 0;
|
||||||
|
views.setViewVisibility(R.id.widget_attachment,
|
||||||
|
hasAttachment ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
|
if (mCursor.getAccountCount() <= 1 || mWidgetView.isPerAccount()) {
|
||||||
|
views.setViewVisibility(R.id.color_chip, View.INVISIBLE);
|
||||||
|
} else {
|
||||||
|
long accountId = mCursor.getLong(EmailWidgetLoader.WIDGET_COLUMN_ACCOUNT_KEY);
|
||||||
|
int colorId = mResourceHelper.getAccountColorId(accountId);
|
||||||
|
if (colorId != ResourceHelper.UNDEFINED_RESOURCE_ID) {
|
||||||
|
// Color defined by resource ID, so, use it
|
||||||
|
views.setViewVisibility(R.id.color_chip, View.VISIBLE);
|
||||||
|
views.setImageViewResource(R.id.color_chip, colorId);
|
||||||
|
} else {
|
||||||
|
// Color not defined by resource ID, nothing we can do, so, hide the chip
|
||||||
|
views.setViewVisibility(R.id.color_chip, View.INVISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set button intents for view, reply, and delete
|
||||||
|
String messageId = mCursor.getString(EmailWidgetLoader.WIDGET_COLUMN_ID);
|
||||||
|
String mailboxId = mCursor.getString(EmailWidgetLoader.WIDGET_COLUMN_MAILBOX_KEY);
|
||||||
|
setFillInIntent(views, R.id.widget_message, COMMAND_URI_VIEW_MESSAGE,
|
||||||
|
messageId, mailboxId);
|
||||||
|
|
||||||
|
return views;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getCount() {
|
public int getCount() {
|
||||||
if (mCursor == null) return 0;
|
if (!isCursorValid()) return 0;
|
||||||
return Math.min(mCursor.getCount(), MAX_MESSAGE_LIST_COUNT);
|
return Math.min(mCursor.getCount(), MAX_MESSAGE_LIST_COUNT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -671,7 +509,7 @@ public class EmailWidget implements RemoteViewsService.RemoteViewsFactory {
|
||||||
|
|
||||||
public void onDeleted() {
|
public void onDeleted() {
|
||||||
if (mLoader != null) {
|
if (mLoader != null) {
|
||||||
mLoader.stopLoading();
|
mLoader.reset();
|
||||||
}
|
}
|
||||||
WidgetManager.getInstance().remove(mWidgetId);
|
WidgetManager.getInstance().remove(mWidgetId);
|
||||||
}
|
}
|
||||||
|
@ -679,7 +517,7 @@ public class EmailWidget implements RemoteViewsService.RemoteViewsFactory {
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
if (mLoader != null) {
|
if (mLoader != null) {
|
||||||
mLoader.stopLoading();
|
mLoader.reset();
|
||||||
}
|
}
|
||||||
WidgetManager.getInstance().remove(mWidgetId);
|
WidgetManager.getInstance().remove(mWidgetId);
|
||||||
}
|
}
|
||||||
|
@ -688,60 +526,46 @@ public class EmailWidget implements RemoteViewsService.RemoteViewsFactory {
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the widget. If the current view is invalid, switch to the next view, then update.
|
||||||
|
*/
|
||||||
|
/* package */ void validateAndUpdate() {
|
||||||
|
new WidgetUpdater(false).execute();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Switch to the next view.
|
* Switch to the next view.
|
||||||
*/
|
*/
|
||||||
/* package */ void switchView() {
|
/* package */ void switchView() {
|
||||||
switchView(false);
|
new WidgetUpdater(true).execute();
|
||||||
}
|
|
||||||
|
|
||||||
private WidgetViewSwitcher switchView(boolean disableLoadAfterSwitchForTest) {
|
|
||||||
WidgetViewSwitcher switcher = new WidgetViewSwitcher(this, disableLoadAfterSwitchForTest);
|
|
||||||
switcher.execute();
|
|
||||||
return switcher;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Switch views synchronously without loading
|
* Update the widget. If {@code switchToNextView} is set true, or the current view is invalid,
|
||||||
|
* switch to the next view.
|
||||||
*/
|
*/
|
||||||
/* package */ void switchViewSyncForTest() {
|
private class WidgetUpdater extends AsyncTask<Void, Void, WidgetView> {
|
||||||
WidgetViewSwitcher switcher = switchView(true);
|
private final WidgetView mCurrentView;
|
||||||
try {
|
private final boolean mSwitchToNextView;
|
||||||
switcher.get();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Assert.fail();
|
|
||||||
} catch (ExecutionException e) {
|
|
||||||
Assert.fail();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public WidgetUpdater(boolean switchToNextView) {
|
||||||
* Utility class to handle switching widget views; in the background, we access the database
|
mCurrentView = mWidgetView;
|
||||||
* to determine account status, etc. In the foreground, we start up the Loader with new
|
mSwitchToNextView = switchToNextView;
|
||||||
* parameters
|
|
||||||
*/
|
|
||||||
private static class WidgetViewSwitcher extends AsyncTask<Void, Void, Void> {
|
|
||||||
private final EmailWidget mWidget;
|
|
||||||
private final boolean mDisableLoadAfterSwitchForTest;
|
|
||||||
|
|
||||||
public WidgetViewSwitcher(EmailWidget widget, boolean disableLoadAfterSwitchForTest) {
|
|
||||||
mWidget = widget;
|
|
||||||
mDisableLoadAfterSwitchForTest = disableLoadAfterSwitchForTest;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Void doInBackground(Void... params) {
|
protected WidgetView doInBackground(Void... params) {
|
||||||
mWidget.switchToNextView();
|
if (mSwitchToNextView || !mCurrentView.isValid(mContext)) {
|
||||||
return null;
|
return mCurrentView.getNext(mContext);
|
||||||
}
|
} else {
|
||||||
|
return mCurrentView; // Reload the same view.
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Void param) {
|
|
||||||
if (isCancelled()) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (!mDisableLoadAfterSwitchForTest) {
|
}
|
||||||
mWidget.loadView();
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(WidgetView nextView) {
|
||||||
|
if (nextView != null) {
|
||||||
|
loadView(nextView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,135 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2011 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.widget;
|
||||||
|
|
||||||
|
import com.android.email.data.ThrottlingCursorLoader;
|
||||||
|
import com.android.emailcommon.provider.EmailContent;
|
||||||
|
import com.android.emailcommon.provider.EmailContent.Account;
|
||||||
|
import com.android.emailcommon.provider.EmailContent.Message;
|
||||||
|
import com.android.emailcommon.provider.EmailContent.MessageColumns;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.CursorWrapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loader for {@link EmailWidget}.
|
||||||
|
*
|
||||||
|
* This loader not only loads the messages, but also:
|
||||||
|
* - The number of accounts.
|
||||||
|
* - The message count shown in the widget header.
|
||||||
|
* It's currently just the same as the message count, but this will be updated to the unread
|
||||||
|
* counts for inboxes.
|
||||||
|
*/
|
||||||
|
/* package */ class EmailWidgetLoader extends ThrottlingCursorLoader {
|
||||||
|
private static final String SORT_TIMESTAMP_DESCENDING = MessageColumns.TIMESTAMP + " DESC";
|
||||||
|
|
||||||
|
// The projection to be used by the WidgetLoader
|
||||||
|
private static final String[] WIDGET_PROJECTION = new String[] {
|
||||||
|
EmailContent.RECORD_ID, MessageColumns.DISPLAY_NAME, MessageColumns.TIMESTAMP,
|
||||||
|
MessageColumns.SUBJECT, MessageColumns.FLAG_READ, MessageColumns.FLAG_FAVORITE,
|
||||||
|
MessageColumns.FLAG_ATTACHMENT, MessageColumns.MAILBOX_KEY, MessageColumns.SNIPPET,
|
||||||
|
MessageColumns.ACCOUNT_KEY, MessageColumns.FLAGS
|
||||||
|
};
|
||||||
|
public static final int WIDGET_COLUMN_ID = 0;
|
||||||
|
public static final int WIDGET_COLUMN_DISPLAY_NAME = 1;
|
||||||
|
public static final int WIDGET_COLUMN_TIMESTAMP = 2;
|
||||||
|
public static final int WIDGET_COLUMN_SUBJECT = 3;
|
||||||
|
public static final int WIDGET_COLUMN_FLAG_READ = 4;
|
||||||
|
public static final int WIDGET_COLUMN_FLAG_FAVORITE = 5;
|
||||||
|
public static final int WIDGET_COLUMN_FLAG_ATTACHMENT = 6;
|
||||||
|
public static final int WIDGET_COLUMN_MAILBOX_KEY = 7;
|
||||||
|
public static final int WIDGET_COLUMN_SNIPPET = 8;
|
||||||
|
public static final int WIDGET_COLUMN_ACCOUNT_KEY = 9;
|
||||||
|
public static final int WIDGET_COLUMN_FLAGS = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The actual data returned by this loader.
|
||||||
|
*/
|
||||||
|
public static class CursorWithCounts extends CursorWrapper {
|
||||||
|
private final int mAccountCount;
|
||||||
|
private final int mMessageCount;
|
||||||
|
|
||||||
|
public CursorWithCounts(Cursor cursor, int accountCount, int messageCount) {
|
||||||
|
super(cursor);
|
||||||
|
mAccountCount = accountCount;
|
||||||
|
mMessageCount = messageCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAccountCount() {
|
||||||
|
return mAccountCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The count that should be shown on the widget header.
|
||||||
|
* Note depending on the view, it may be the unread count, which is different from
|
||||||
|
* the record count (i.e. {@link #getCount()}}.
|
||||||
|
*/
|
||||||
|
public int getMessageCount() {
|
||||||
|
return mMessageCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Context mContext;
|
||||||
|
|
||||||
|
private WidgetView mLoadingWidgetView;
|
||||||
|
|
||||||
|
public EmailWidgetLoader(Context context) {
|
||||||
|
super(context, Message.CONTENT_URI, WIDGET_PROJECTION, null,
|
||||||
|
null, SORT_TIMESTAMP_DESCENDING);
|
||||||
|
mContext = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor loadInBackground() {
|
||||||
|
final Cursor messagesCursor = super.loadInBackground();
|
||||||
|
|
||||||
|
// Reset the notification Uri to our Message table notifier URI
|
||||||
|
messagesCursor.setNotificationUri(mContext.getContentResolver(), Message.NOTIFIER_URI);
|
||||||
|
|
||||||
|
final int accountCount = EmailContent.count(mContext, Account.CONTENT_URI);
|
||||||
|
|
||||||
|
// TODO Use correct count -- e.g. unread count for inboxes, not total count.
|
||||||
|
final int messageCount = messagesCursor.getCount();
|
||||||
|
|
||||||
|
return new CursorWithCounts(messagesCursor, accountCount, messageCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop any pending load, reset selection parameters, and start loading.
|
||||||
|
*
|
||||||
|
* Must be called from the UI thread
|
||||||
|
*
|
||||||
|
* @param view the current ViewType
|
||||||
|
*/
|
||||||
|
public void load(WidgetView view) {
|
||||||
|
reset();
|
||||||
|
mLoadingWidgetView = view;
|
||||||
|
setSelection(view.getSelection(mContext));
|
||||||
|
setSelectionArgs(view.getSelectionArgs());
|
||||||
|
startLoading();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the {@link WidgetView} that is (being) loaded.
|
||||||
|
*
|
||||||
|
* Must be called from the UI thread
|
||||||
|
*/
|
||||||
|
public WidgetView getLoadingWidgetView() {
|
||||||
|
return mLoadingWidgetView;
|
||||||
|
}
|
||||||
|
}
|
|
@ -49,13 +49,13 @@ public class WidgetManager {
|
||||||
public synchronized void updateAllWidgets() {
|
public synchronized void updateAllWidgets() {
|
||||||
for (EmailWidget widget: mWidgets.values()) {
|
for (EmailWidget widget: mWidgets.values()) {
|
||||||
// Anything could have changed; update widget & validate the current view
|
// Anything could have changed; update widget & validate the current view
|
||||||
widget.updateWidget(true);
|
widget.validateAndUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void getOrCreateWidgets(Context context, int[] widgetIds) {
|
public synchronized void getOrCreateWidgets(Context context, int[] widgetIds) {
|
||||||
for (int widgetId : widgetIds) {
|
for (int widgetId : widgetIds) {
|
||||||
getOrCreateWidget(context, widgetId).updateHeader();
|
getOrCreateWidget(context, widgetId).validateAndUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,8 +66,8 @@ public class WidgetManager {
|
||||||
Log.d(EmailWidget.TAG, "Creating EmailWidget for id #" + widgetId);
|
Log.d(EmailWidget.TAG, "Creating EmailWidget for id #" + widgetId);
|
||||||
}
|
}
|
||||||
widget = new EmailWidget(context, widgetId);
|
widget = new EmailWidget(context, widgetId);
|
||||||
widget.init();
|
|
||||||
WidgetManager.getInstance().put(widgetId, widget);
|
WidgetManager.getInstance().put(widgetId, widget);
|
||||||
|
widget.start();
|
||||||
}
|
}
|
||||||
return widget;
|
return widget;
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,7 @@ public class WidgetManager {
|
||||||
int n = 0;
|
int n = 0;
|
||||||
for (EmailWidget widget : mWidgets.values()) {
|
for (EmailWidget widget : mWidgets.values()) {
|
||||||
writer.println("Widget #" + (++n));
|
writer.println("Widget #" + (++n));
|
||||||
writer.println(" ViewType=" + widget.mViewType);
|
writer.println(" View=" + widget.mWidgetView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,202 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2011 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.widget;
|
||||||
|
|
||||||
|
import com.android.email.R;
|
||||||
|
import com.android.emailcommon.provider.EmailContent;
|
||||||
|
import com.android.emailcommon.provider.EmailContent.Account;
|
||||||
|
import com.android.emailcommon.provider.EmailContent.AccountColumns;
|
||||||
|
import com.android.emailcommon.provider.EmailContent.Message;
|
||||||
|
import com.android.emailcommon.utility.Utility;
|
||||||
|
|
||||||
|
import android.content.ContentUris;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the "view" of the widget.
|
||||||
|
*
|
||||||
|
* It's a {@link ViewType} + mutable fields. (e.g. account id/name)
|
||||||
|
*/
|
||||||
|
/* package */ class WidgetView {
|
||||||
|
private static final String SORT_ID_ASCENDING = AccountColumns.ID + " ASC";
|
||||||
|
private static final String[] ID_NAME_PROJECTION = {Account.RECORD_ID, Account.DISPLAY_NAME};
|
||||||
|
private static final int ID_NAME_COLUMN_ID = 0;
|
||||||
|
private static final int ID_NAME_COLUMN_NAME = 1;
|
||||||
|
|
||||||
|
private static enum ViewType {
|
||||||
|
TYPE_ALL_UNREAD(false, Message.UNREAD_SELECTION, R.string.widget_unread),
|
||||||
|
TYPE_ALL_STARRED(false, Message.ALL_FAVORITE_SELECTION, R.string.widget_starred),
|
||||||
|
TYPE_ALL_INBOX(false, Message.INBOX_SELECTION, R.string.widget_all_mail),
|
||||||
|
TYPE_ACCOUNT_INBOX(true, Message.PER_ACCOUNT_INBOX_SELECTION, 0) {
|
||||||
|
@Override public String getTitle(Context context, String accountName) {
|
||||||
|
return accountName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public String[] getSelectionArgs(long accountId) {
|
||||||
|
return new String[]{Long.toString(accountId)};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final boolean mIsPerAccount;
|
||||||
|
private final String mSelection;
|
||||||
|
private final int mTitleResource;
|
||||||
|
|
||||||
|
ViewType(boolean isPerAccount, String selection, int titleResource) {
|
||||||
|
mIsPerAccount = isPerAccount;
|
||||||
|
mSelection = selection;
|
||||||
|
mTitleResource = titleResource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle(Context context, String accountName) {
|
||||||
|
return context.getString(mTitleResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSelection() {
|
||||||
|
return mSelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getSelectionArgs(long accountId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ static final WidgetView ALL_UNREAD = new WidgetView(ViewType.TYPE_ALL_UNREAD);
|
||||||
|
/* package */ static final WidgetView ALL_STARRED = new WidgetView(ViewType.TYPE_ALL_STARRED);
|
||||||
|
/* package */ static final WidgetView ALL_INBOX = new WidgetView(ViewType.TYPE_ALL_INBOX);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The initial view will be the *next* of ALL_STARRED -- see {@link #getNext}.
|
||||||
|
*/
|
||||||
|
public static final WidgetView UNINITIALIZED_VIEW = ALL_STARRED;
|
||||||
|
|
||||||
|
private final ViewType mViewType;
|
||||||
|
/** Account ID -- set only when isPerAccount */
|
||||||
|
private final long mAccountId;
|
||||||
|
/** Account name -- set only when isPerAccount */
|
||||||
|
private final String mAccountName;
|
||||||
|
|
||||||
|
private WidgetView(ViewType viewType) {
|
||||||
|
this(viewType, 0, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private WidgetView(ViewType viewType, long accountId, String accountName) {
|
||||||
|
mViewType = viewType;
|
||||||
|
mAccountId = accountId;
|
||||||
|
mAccountName = accountName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPerAccount() {
|
||||||
|
return mViewType.mIsPerAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle(Context context) {
|
||||||
|
return mViewType.getTitle(context, mAccountName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSelection(Context context) {
|
||||||
|
return mViewType.getSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getSelectionArgs() {
|
||||||
|
return mViewType.getSelectionArgs(mAccountId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switch to the "next" view.
|
||||||
|
*
|
||||||
|
* Views rotate in this order:
|
||||||
|
* - {@link #ALL_STARRED}
|
||||||
|
* - {@link #ALL_INBOX} -- this will be skipped if # of accounts <= 1
|
||||||
|
* - Inbox for account 1
|
||||||
|
* - Inbox for account 2
|
||||||
|
* - :
|
||||||
|
* - {@link #ALL_UNREAD}
|
||||||
|
* - Go back to {@link #ALL_STARRED}.
|
||||||
|
*
|
||||||
|
* Note the initial view is always the next of {@link #ALL_STARRED}.
|
||||||
|
*/
|
||||||
|
public WidgetView getNext(Context context) {
|
||||||
|
if (mViewType == ViewType.TYPE_ALL_UNREAD) {
|
||||||
|
return ALL_STARRED;
|
||||||
|
}
|
||||||
|
if (mViewType == ViewType.TYPE_ALL_STARRED) {
|
||||||
|
// If we're in starred and there is more than one account, go to "all mail"
|
||||||
|
// Otherwise, fall through to the accounts themselves
|
||||||
|
if (EmailContent.count(context, Account.CONTENT_URI) > 1) {
|
||||||
|
return ALL_INBOX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final long nextAccountIdStart;
|
||||||
|
if (mViewType == ViewType.TYPE_ALL_INBOX) {
|
||||||
|
nextAccountIdStart = -1;
|
||||||
|
} else { // TYPE_ACCOUNT_INBOX
|
||||||
|
nextAccountIdStart = mAccountId + 1;
|
||||||
|
}
|
||||||
|
Cursor c = context.getContentResolver().query(Account.CONTENT_URI, ID_NAME_PROJECTION,
|
||||||
|
"_id>=?", new String[] {Long.toString(nextAccountIdStart)}, SORT_ID_ASCENDING);
|
||||||
|
|
||||||
|
final long nextAccountId;
|
||||||
|
final String nextAccountName;
|
||||||
|
try {
|
||||||
|
if (c.moveToFirst()) {
|
||||||
|
return new WidgetView(ViewType.TYPE_ACCOUNT_INBOX, c.getLong(ID_NAME_COLUMN_ID),
|
||||||
|
c.getString(ID_NAME_COLUMN_NAME));
|
||||||
|
} else {
|
||||||
|
return ALL_UNREAD;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
c.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the current view is valid. The following rules determine if a view is
|
||||||
|
* considered valid:
|
||||||
|
* 1. {@link ViewType#TYPE_ALL_STARRED} and {@link ViewType#TYPE_ALL_UNREAD} are always
|
||||||
|
* valid.
|
||||||
|
* 2. If the view is {@link ViewType#TYPE_ALL_INBOX}, returns <code>true</code> if more than
|
||||||
|
* one account is defined. Otherwise, returns <code>false</code>.
|
||||||
|
* 3. If the view is {@link ViewType#TYPE_ACCOUNT_INBOX}, returns <code>true</code> if the
|
||||||
|
* account is defined. Otherwise, returns <code>false</code>.
|
||||||
|
*/
|
||||||
|
public boolean isValid(Context context) {
|
||||||
|
switch(mViewType) {
|
||||||
|
case TYPE_ALL_INBOX:
|
||||||
|
// "all inbox" is valid only if there is more than one account
|
||||||
|
return (EmailContent.count(context, Account.CONTENT_URI) > 1);
|
||||||
|
case TYPE_ACCOUNT_INBOX:
|
||||||
|
// Ensure current account still exists
|
||||||
|
Uri uri = ContentUris.withAppendedId(Account.CONTENT_URI, mAccountId);
|
||||||
|
return Utility.getFirstRowLong(context, uri,
|
||||||
|
EmailContent.ID_PROJECTION, null, null, null,
|
||||||
|
EmailContent.ID_PROJECTION_COLUMN, null) != null;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder("WidgetView:type=");
|
||||||
|
sb.append(mViewType);
|
||||||
|
sb.append(" account=");
|
||||||
|
sb.append(mAccountId);
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,6 +26,7 @@ import com.android.emailcommon.provider.EmailContent.Mailbox;
|
||||||
import com.android.emailcommon.provider.EmailContent.Message;
|
import com.android.emailcommon.provider.EmailContent.Message;
|
||||||
import com.android.emailcommon.utility.Utility;
|
import com.android.emailcommon.utility.Utility;
|
||||||
|
|
||||||
|
import android.content.ContentUris;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.test.MoreAsserts;
|
import android.test.MoreAsserts;
|
||||||
|
@ -72,6 +73,14 @@ public class ProviderTestUtils extends Assert {
|
||||||
return account;
|
return account;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lightweight way of deleting an account for testing.
|
||||||
|
*/
|
||||||
|
public static void deleteAccount(Context context, long accountId) {
|
||||||
|
context.getContentResolver().delete(ContentUris.withAppendedId(
|
||||||
|
EmailContent.Account.CONTENT_URI, accountId), null, null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a hostauth record for test purposes
|
* Create a hostauth record for test purposes
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,141 +0,0 @@
|
||||||
/* Copyright (C) 2011 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.widget;
|
|
||||||
|
|
||||||
import com.android.email.provider.EmailProvider;
|
|
||||||
import com.android.email.provider.ProviderTestUtils;
|
|
||||||
import com.android.emailcommon.provider.EmailContent;
|
|
||||||
import com.android.emailcommon.provider.EmailContent.Account;
|
|
||||||
import com.android.emailcommon.provider.EmailContent.Mailbox;
|
|
||||||
import com.android.emailcommon.provider.EmailContent.Message;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.test.ProviderTestCase2;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests of EmailWidget
|
|
||||||
*
|
|
||||||
* You can run this entire test case with:
|
|
||||||
* runtest -c com.android.email.widget.EmailWidget email
|
|
||||||
*/
|
|
||||||
public class EmailWidgetTests extends ProviderTestCase2<EmailProvider> {
|
|
||||||
private Context mMockContext;
|
|
||||||
|
|
||||||
public EmailWidgetTests() {
|
|
||||||
super(EmailProvider.class, EmailContent.AUTHORITY);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
super.setUp();
|
|
||||||
mMockContext = getMockContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void tearDown() throws Exception {
|
|
||||||
super.tearDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getMessageCount(ViewType view) {
|
|
||||||
return EmailContent.count(mMockContext, Message.CONTENT_URI, view.selection,
|
|
||||||
view.selectionArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Message createMessage(Context c, Mailbox b, boolean starred, boolean read,
|
|
||||||
int flagLoaded) {
|
|
||||||
Message message = ProviderTestUtils.setupMessage(
|
|
||||||
"1", b.mAccountKey, b.mId, true, false, c, starred, read);
|
|
||||||
message.mFlagLoaded = flagLoaded;
|
|
||||||
message.save(c);
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testWidgetSwitcher() {
|
|
||||||
// Create account
|
|
||||||
ProviderTestUtils.setupAccount("account1", true, mMockContext);
|
|
||||||
|
|
||||||
// Create a widget
|
|
||||||
EmailWidget widget = new EmailWidget(mMockContext, 1);
|
|
||||||
// Since there is one account, this should switch to the ACCOUNT view
|
|
||||||
widget.switchViewSyncForTest();
|
|
||||||
assertEquals(ViewType.ACCOUNT, widget.mViewType);
|
|
||||||
|
|
||||||
// Create account
|
|
||||||
ProviderTestUtils.setupAccount("account2", true, mMockContext);
|
|
||||||
// Create a widget
|
|
||||||
widget = new EmailWidget(mMockContext, 2);
|
|
||||||
// Since there are two accounts, this should switch to the ALL_INBOX view
|
|
||||||
widget.switchViewSyncForTest();
|
|
||||||
assertEquals(ViewType.ALL_INBOX, widget.mViewType);
|
|
||||||
|
|
||||||
// The next two switches should be to the two accounts
|
|
||||||
widget.switchViewSyncForTest();
|
|
||||||
assertEquals(ViewType.ACCOUNT, widget.mViewType);
|
|
||||||
widget.switchViewSyncForTest();
|
|
||||||
assertEquals(ViewType.ACCOUNT, widget.mViewType);
|
|
||||||
widget.switchViewSyncForTest();
|
|
||||||
assertEquals(ViewType.UNREAD, widget.mViewType);
|
|
||||||
widget.switchViewSyncForTest();
|
|
||||||
assertEquals(ViewType.STARRED, widget.mViewType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test the message counts returned by the ViewType selectors.
|
|
||||||
*/
|
|
||||||
public void testCursorCount() {
|
|
||||||
// Create 2 accounts
|
|
||||||
Account a1 = ProviderTestUtils.setupAccount("account1", true, mMockContext);
|
|
||||||
Account a2 = ProviderTestUtils.setupAccount("account2", true, mMockContext);
|
|
||||||
|
|
||||||
// Create 2 mailboxes for each account
|
|
||||||
Mailbox b1 = ProviderTestUtils.setupMailbox(
|
|
||||||
"box1", a1.mId, true, mMockContext, Mailbox.TYPE_INBOX);
|
|
||||||
Mailbox b2 = ProviderTestUtils.setupMailbox(
|
|
||||||
"box2", a1.mId, true, mMockContext, Mailbox.TYPE_OUTBOX);
|
|
||||||
Mailbox b3 = ProviderTestUtils.setupMailbox(
|
|
||||||
"box3", a2.mId, true, mMockContext, Mailbox.TYPE_INBOX);
|
|
||||||
Mailbox b4 = ProviderTestUtils.setupMailbox(
|
|
||||||
"box4", a2.mId, true, mMockContext, Mailbox.TYPE_OUTBOX);
|
|
||||||
Mailbox bt = ProviderTestUtils.setupMailbox(
|
|
||||||
"boxT", a2.mId, true, mMockContext, Mailbox.TYPE_TRASH);
|
|
||||||
|
|
||||||
// Create some messages
|
|
||||||
// b1 (account 1, inbox): 2 messages, including 1 starred, 1 unloaded
|
|
||||||
Message m11 = createMessage(mMockContext, b1, true, false, Message.FLAG_LOADED_COMPLETE);
|
|
||||||
Message m12 = createMessage(mMockContext, b1, false, false, Message.FLAG_LOADED_UNLOADED);
|
|
||||||
|
|
||||||
// b2 (account 1, outbox): 2 messages, including 1 starred
|
|
||||||
Message m21 = createMessage(mMockContext, b2, false, false, Message.FLAG_LOADED_COMPLETE);
|
|
||||||
Message m22 = createMessage(mMockContext, b2, true, true, Message.FLAG_LOADED_COMPLETE);
|
|
||||||
|
|
||||||
// b3 (account 2, inbox): 4 messages, including 1 starred, 1 unloaded
|
|
||||||
Message m31 = createMessage(mMockContext, b3, false, false, Message.FLAG_LOADED_COMPLETE);
|
|
||||||
Message m32 = createMessage(mMockContext, b3, false, true, Message.FLAG_LOADED_COMPLETE);
|
|
||||||
Message m33 = createMessage(mMockContext, b3, true, true, Message.FLAG_LOADED_COMPLETE);
|
|
||||||
Message m34 = createMessage(mMockContext, b3, true, true, Message.FLAG_LOADED_UNLOADED);
|
|
||||||
|
|
||||||
// b4 (account 2, outbox) has no messages.
|
|
||||||
|
|
||||||
// bt (account 2, trash): 3 messages, including 2 starred
|
|
||||||
Message mt1 = createMessage(mMockContext, bt, true, false, Message.FLAG_LOADED_COMPLETE);
|
|
||||||
Message mt2 = createMessage(mMockContext, bt, true, true, Message.FLAG_LOADED_COMPLETE);
|
|
||||||
Message mt3 = createMessage(mMockContext, bt, false, false, Message.FLAG_LOADED_COMPLETE);
|
|
||||||
|
|
||||||
assertEquals(4, getMessageCount(ViewType.ALL_INBOX));
|
|
||||||
assertEquals(3, getMessageCount(ViewType.STARRED));
|
|
||||||
assertEquals(2, getMessageCount(ViewType.UNREAD));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,215 @@
|
||||||
|
/* Copyright (C) 2011 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.widget;
|
||||||
|
|
||||||
|
import com.android.email.provider.EmailProvider;
|
||||||
|
import com.android.email.provider.ProviderTestUtils;
|
||||||
|
import com.android.emailcommon.provider.EmailContent;
|
||||||
|
import com.android.emailcommon.provider.EmailContent.Account;
|
||||||
|
import com.android.emailcommon.provider.EmailContent.Mailbox;
|
||||||
|
import com.android.emailcommon.provider.EmailContent.Message;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.test.ProviderTestCase2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests of EmailWidget
|
||||||
|
*
|
||||||
|
* You can run this entire test case with:
|
||||||
|
* runtest -c com.android.email.widget.WidgetView email
|
||||||
|
*/
|
||||||
|
public class WidgetViewTests extends ProviderTestCase2<EmailProvider> {
|
||||||
|
private Context mMockContext;
|
||||||
|
|
||||||
|
public WidgetViewTests() {
|
||||||
|
super(EmailProvider.class, EmailContent.AUTHORITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
|
mMockContext = getMockContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tearDown() throws Exception {
|
||||||
|
super.tearDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getMessageCount(WidgetView view) {
|
||||||
|
return EmailContent.count(mMockContext, Message.CONTENT_URI,
|
||||||
|
view.getSelection(mMockContext), view.getSelectionArgs());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Message createMessage(Context c, Mailbox b, boolean starred, boolean read,
|
||||||
|
int flagLoaded) {
|
||||||
|
Message message = ProviderTestUtils.setupMessage(
|
||||||
|
"1", b.mAccountKey, b.mId, true, false, c, starred, read);
|
||||||
|
message.mFlagLoaded = flagLoaded;
|
||||||
|
message.save(c);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGetNext() {
|
||||||
|
// Test with 1 account.
|
||||||
|
final Account a1 = ProviderTestUtils.setupAccount("account1", true, mMockContext);
|
||||||
|
|
||||||
|
WidgetView view = WidgetView.ALL_UNREAD;
|
||||||
|
|
||||||
|
// all unread -> all starred
|
||||||
|
view = view.getNext(mMockContext);
|
||||||
|
assertEquals(WidgetView.ALL_STARRED, view);
|
||||||
|
|
||||||
|
// all starred -> account 1 inbox
|
||||||
|
view = view.getNext(mMockContext);
|
||||||
|
assertTrue(view.isPerAccount());
|
||||||
|
assertEquals(Long.toString(a1.mId), view.getSelectionArgs()[0]);
|
||||||
|
|
||||||
|
// account 1 inbox -> all unread
|
||||||
|
view = view.getNext(mMockContext);
|
||||||
|
assertEquals(WidgetView.ALL_UNREAD, view);
|
||||||
|
|
||||||
|
// Next, test with 2 accounts.
|
||||||
|
final Account a2 = ProviderTestUtils.setupAccount("account2", true, mMockContext);
|
||||||
|
|
||||||
|
// Still all unread
|
||||||
|
assertEquals(WidgetView.ALL_UNREAD, view);
|
||||||
|
|
||||||
|
// all unread -> all starred
|
||||||
|
view = view.getNext(mMockContext);
|
||||||
|
assertEquals(WidgetView.ALL_STARRED, view);
|
||||||
|
|
||||||
|
// all starred -> all inboxes, as there are more than 1 account.
|
||||||
|
view = view.getNext(mMockContext);
|
||||||
|
assertEquals(WidgetView.ALL_INBOX, view);
|
||||||
|
|
||||||
|
// all inbox -> account 1 inbox
|
||||||
|
view = view.getNext(mMockContext);
|
||||||
|
assertTrue(view.isPerAccount());
|
||||||
|
assertEquals(Long.toString(a1.mId), view.getSelectionArgs()[0]);
|
||||||
|
|
||||||
|
// account 1 inbox -> account 2 inbox
|
||||||
|
view = view.getNext(mMockContext);
|
||||||
|
assertTrue(view.isPerAccount());
|
||||||
|
assertEquals(Long.toString(a2.mId), view.getSelectionArgs()[0]);
|
||||||
|
|
||||||
|
// account 2 inbox -> all unread
|
||||||
|
view = view.getNext(mMockContext);
|
||||||
|
assertEquals(WidgetView.ALL_UNREAD, view);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testIsValid() {
|
||||||
|
// with 0 accounts
|
||||||
|
assertTrue(WidgetView.ALL_UNREAD.isValid(mMockContext));
|
||||||
|
assertTrue(WidgetView.ALL_STARRED.isValid(mMockContext));
|
||||||
|
assertFalse(WidgetView.ALL_INBOX.isValid(mMockContext));
|
||||||
|
|
||||||
|
// Test with 1 account.
|
||||||
|
final Account a1 = ProviderTestUtils.setupAccount("account1", true, mMockContext);
|
||||||
|
assertTrue(WidgetView.ALL_UNREAD.isValid(mMockContext));
|
||||||
|
assertTrue(WidgetView.ALL_STARRED.isValid(mMockContext));
|
||||||
|
assertFalse(WidgetView.ALL_INBOX.isValid(mMockContext)); // only 1 account -- still invalid
|
||||||
|
|
||||||
|
final WidgetView account1View = WidgetView.ALL_INBOX.getNext(mMockContext);
|
||||||
|
assertEquals(Long.toString(a1.mId), account1View.getSelectionArgs()[0]);
|
||||||
|
assertTrue(account1View.isValid(mMockContext));
|
||||||
|
|
||||||
|
// Test with 2 accounts.
|
||||||
|
final Account a2 = ProviderTestUtils.setupAccount("account2", true, mMockContext);
|
||||||
|
assertTrue(WidgetView.ALL_UNREAD.isValid(mMockContext));
|
||||||
|
assertTrue(WidgetView.ALL_STARRED.isValid(mMockContext));
|
||||||
|
assertTrue(WidgetView.ALL_INBOX.isValid(mMockContext)); // now it's valid
|
||||||
|
|
||||||
|
final WidgetView account2View = account1View.getNext(mMockContext);
|
||||||
|
assertEquals(Long.toString(a2.mId), account2View.getSelectionArgs()[0]);
|
||||||
|
assertTrue(account2View.isValid(mMockContext));
|
||||||
|
|
||||||
|
// Remove account 1
|
||||||
|
ProviderTestUtils.deleteAccount(mMockContext, a1.mId);
|
||||||
|
|
||||||
|
assertTrue(WidgetView.ALL_UNREAD.isValid(mMockContext));
|
||||||
|
assertTrue(WidgetView.ALL_STARRED.isValid(mMockContext));
|
||||||
|
assertFalse(WidgetView.ALL_INBOX.isValid(mMockContext)); // only 1 account -- now invalid
|
||||||
|
|
||||||
|
assertFalse(account1View.isValid(mMockContext));
|
||||||
|
assertTrue(account2View.isValid(mMockContext));
|
||||||
|
|
||||||
|
// Remove account 2
|
||||||
|
ProviderTestUtils.deleteAccount(mMockContext, a2.mId);
|
||||||
|
|
||||||
|
assertTrue(WidgetView.ALL_UNREAD.isValid(mMockContext));
|
||||||
|
assertTrue(WidgetView.ALL_STARRED.isValid(mMockContext));
|
||||||
|
assertFalse(WidgetView.ALL_INBOX.isValid(mMockContext)); // still invalid
|
||||||
|
|
||||||
|
assertFalse(account1View.isValid(mMockContext));
|
||||||
|
assertFalse(account2View.isValid(mMockContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the message counts returned by the ViewType selectors.
|
||||||
|
*/
|
||||||
|
public void testCursorCount() {
|
||||||
|
// Create 2 accounts
|
||||||
|
Account a1 = ProviderTestUtils.setupAccount("account1", true, mMockContext);
|
||||||
|
Account a2 = ProviderTestUtils.setupAccount("account2", true, mMockContext);
|
||||||
|
|
||||||
|
// Create 2 mailboxes for each account
|
||||||
|
Mailbox b11 = ProviderTestUtils.setupMailbox(
|
||||||
|
"box11", a1.mId, true, mMockContext, Mailbox.TYPE_INBOX);
|
||||||
|
Mailbox b12 = ProviderTestUtils.setupMailbox(
|
||||||
|
"box12", a1.mId, true, mMockContext, Mailbox.TYPE_OUTBOX);
|
||||||
|
Mailbox b21 = ProviderTestUtils.setupMailbox(
|
||||||
|
"box21", a2.mId, true, mMockContext, Mailbox.TYPE_INBOX);
|
||||||
|
Mailbox b22 = ProviderTestUtils.setupMailbox(
|
||||||
|
"box22", a2.mId, true, mMockContext, Mailbox.TYPE_OUTBOX);
|
||||||
|
Mailbox b2t = ProviderTestUtils.setupMailbox(
|
||||||
|
"box2T", a2.mId, true, mMockContext, Mailbox.TYPE_TRASH);
|
||||||
|
|
||||||
|
// Create some messages
|
||||||
|
// b11 (account 1, inbox): 2 messages, including 1 starred, 1 unloaded
|
||||||
|
Message m11a = createMessage(mMockContext, b11, true, false, Message.FLAG_LOADED_COMPLETE);
|
||||||
|
Message m11b = createMessage(mMockContext, b11, false, false, Message.FLAG_LOADED_UNLOADED);
|
||||||
|
|
||||||
|
// b12 (account 1, outbox): 2 messages, including 1 starred
|
||||||
|
Message m12a = createMessage(mMockContext, b12, false, false, Message.FLAG_LOADED_COMPLETE);
|
||||||
|
Message m12b = createMessage(mMockContext, b12, true, true, Message.FLAG_LOADED_COMPLETE);
|
||||||
|
|
||||||
|
// b21 (account 2, inbox): 4 messages, including 1 starred, 1 unloaded
|
||||||
|
Message m21a = createMessage(mMockContext, b21, false, false, Message.FLAG_LOADED_COMPLETE);
|
||||||
|
Message m21b = createMessage(mMockContext, b21, false, true, Message.FLAG_LOADED_COMPLETE);
|
||||||
|
Message m21c = createMessage(mMockContext, b21, true, true, Message.FLAG_LOADED_COMPLETE);
|
||||||
|
Message m21d = createMessage(mMockContext, b21, true, true, Message.FLAG_LOADED_UNLOADED);
|
||||||
|
|
||||||
|
// b22 (account 2, outbox) has no messages.
|
||||||
|
|
||||||
|
// bt (account 2, trash): 3 messages, including 2 starred
|
||||||
|
Message mt1 = createMessage(mMockContext, b2t, true, false, Message.FLAG_LOADED_COMPLETE);
|
||||||
|
Message mt2 = createMessage(mMockContext, b2t, true, true, Message.FLAG_LOADED_COMPLETE);
|
||||||
|
Message mt3 = createMessage(mMockContext, b2t, false, false, Message.FLAG_LOADED_COMPLETE);
|
||||||
|
|
||||||
|
assertEquals(4, getMessageCount(WidgetView.ALL_INBOX));
|
||||||
|
assertEquals(3, getMessageCount(WidgetView.ALL_STARRED));
|
||||||
|
assertEquals(2, getMessageCount(WidgetView.ALL_UNREAD));
|
||||||
|
|
||||||
|
final WidgetView account1View = WidgetView.ALL_INBOX.getNext(mMockContext);
|
||||||
|
assertEquals(Long.toString(a1.mId), account1View.getSelectionArgs()[0]);
|
||||||
|
assertEquals(1, getMessageCount(account1View));
|
||||||
|
|
||||||
|
final WidgetView account2View = account1View.getNext(mMockContext);
|
||||||
|
assertEquals(Long.toString(a2.mId), account2View.getSelectionArgs()[0]);
|
||||||
|
assertEquals(3, getMessageCount(account2View));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue