Merge "Refactoring widget part 2"
This commit is contained in:
commit
fc8a65aecf
|
@ -17,28 +17,23 @@
|
|||
package com.android.email.widget;
|
||||
|
||||
import com.android.email.Email;
|
||||
import com.android.email.UiUtilities;
|
||||
import com.android.email.R;
|
||||
import com.android.email.ResourceHelper;
|
||||
import com.android.email.UiUtilities;
|
||||
import com.android.email.activity.MessageCompose;
|
||||
import com.android.email.activity.Welcome;
|
||||
import com.android.email.data.ThrottlingCursorLoader;
|
||||
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.Message;
|
||||
import com.android.emailcommon.provider.EmailContent.MessageColumns;
|
||||
import com.android.emailcommon.utility.Utility;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.Loader;
|
||||
import android.content.Loader.OnLoadCompleteListener;
|
||||
import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Typeface;
|
||||
|
@ -59,11 +54,17 @@ import android.widget.RemoteViews;
|
|||
import android.widget.RemoteViewsService;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
public class EmailWidget implements RemoteViewsService.RemoteViewsFactory {
|
||||
/**
|
||||
* The email widget.
|
||||
*
|
||||
* 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";
|
||||
|
||||
/**
|
||||
|
@ -96,16 +97,8 @@ public class EmailWidget implements RemoteViewsService.RemoteViewsFactory {
|
|||
private static final Uri COMMAND_URI_VIEW_MESSAGE =
|
||||
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 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 sConfigureText;
|
||||
private static int sSenderFontSize;
|
||||
|
@ -115,48 +108,25 @@ public class EmailWidget implements RemoteViewsService.RemoteViewsFactory {
|
|||
private static int sLightTextColor;
|
||||
|
||||
private final Context mContext;
|
||||
private final ContentResolver mResolver;
|
||||
private final AppWidgetManager mWidgetManager;
|
||||
|
||||
// The widget identifier
|
||||
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)
|
||||
private ViewCursorLoader mLoader;
|
||||
private final EmailWidgetLoader mLoader;
|
||||
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
|
||||
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
|
||||
};
|
||||
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;
|
||||
/** The current view type */
|
||||
/* package */ WidgetView mWidgetView = WidgetView.UNINITIALIZED_VIEW;
|
||||
|
||||
public EmailWidget(Context context, int _widgetId) {
|
||||
super();
|
||||
|
@ -164,11 +134,11 @@ public class EmailWidget implements RemoteViewsService.RemoteViewsFactory {
|
|||
Log.d(TAG, "Creating EmailWidget with id = " + _widgetId);
|
||||
}
|
||||
mContext = context.getApplicationContext();
|
||||
mResolver = mContext.getContentResolver();
|
||||
mWidgetManager = AppWidgetManager.getInstance(mContext);
|
||||
|
||||
mWidgetId = _widgetId;
|
||||
mLoader = new ViewCursorLoader();
|
||||
mLoader = new EmailWidgetLoader(mContext);
|
||||
mLoader.registerListener(0, this);
|
||||
if (sSubjectSnippetDivider == null) {
|
||||
// Initialize string, color, dimension resources
|
||||
Resources res = mContext.getResources();
|
||||
|
@ -185,169 +155,38 @@ public class EmailWidget implements RemoteViewsService.RemoteViewsFactory {
|
|||
mResourceHelper = ResourceHelper.getInstance(mContext);
|
||||
}
|
||||
|
||||
public void updateWidget(boolean validateView) {
|
||||
new WidgetUpdateTask().execute(validateView);
|
||||
public void start() {
|
||||
// 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...)
|
||||
* 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.
|
||||
* Called when the loader finished loading data. Update the widget.
|
||||
*/
|
||||
private final class WidgetUpdateTask extends AsyncTask<Boolean, Void, Boolean> {
|
||||
@Override
|
||||
protected Boolean doInBackground(Boolean... validateView) {
|
||||
mAccountCount = EmailContent.count(mContext, EmailContent.Account.CONTENT_URI);
|
||||
// If displaying invalid view, switch to the next view
|
||||
return !validateView[0] || isViewValid();
|
||||
}
|
||||
@Override
|
||||
public void onLoadComplete(Loader<Cursor> loader, Cursor cursor) {
|
||||
// Save away the cursor
|
||||
mCursor = (EmailWidgetLoader.CursorWithCounts) cursor;
|
||||
mWidgetView = mLoader.getLoadingWidgetView();
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean isValidView) {
|
||||
updateHeader();
|
||||
if (!isValidView) {
|
||||
switchView();
|
||||
}
|
||||
}
|
||||
RemoteViews views = new RemoteViews(mContext.getPackageName(), R.layout.widget);
|
||||
updateHeader();
|
||||
setupTitleAndCount(views);
|
||||
mWidgetManager.partiallyUpdateAppWidget(mWidgetId, views);
|
||||
mWidgetManager.notifyAppWidgetViewDataChanged(mWidgetId, R.id.message_list);
|
||||
}
|
||||
|
||||
/**
|
||||
* The ThrottlingCursorLoader does all of the heavy lifting in managing the data loading
|
||||
* task; all we need is to register a listener so that we're notified when the load is
|
||||
* complete.
|
||||
* Start loading the data. At this point nothing on the widget changes -- the current view
|
||||
* will remain valid until the loader loads the latest data.
|
||||
*/
|
||||
private final class ViewCursorLoader extends ThrottlingCursorLoader {
|
||||
protected ViewCursorLoader() {
|
||||
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;
|
||||
private void loadView(WidgetView view) {
|
||||
mLoader.load(view);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -451,11 +290,11 @@ public class EmailWidget implements RemoteViewsService.RemoteViewsFactory {
|
|||
|
||||
private void setupTitleAndCount(RemoteViews views) {
|
||||
// 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);
|
||||
String count = "";
|
||||
if (mCursorCount != TOTAL_COUNT_UNKNOWN) {
|
||||
count = UiUtilities.getMessageCountForUi(mContext, mCursor.getCount(), false);
|
||||
if (isCursorValid()) {
|
||||
count = UiUtilities.getMessageCountForUi(mContext, mCursor.getMessageCount(), false);
|
||||
}
|
||||
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
|
||||
* message list)
|
||||
*/
|
||||
public void updateHeader() {
|
||||
private void updateHeader() {
|
||||
if (Email.DEBUG) {
|
||||
Log.d(TAG, "updateWidget " + mWidgetId);
|
||||
}
|
||||
|
@ -480,7 +319,7 @@ public class EmailWidget implements RemoteViewsService.RemoteViewsFactory {
|
|||
|
||||
setupTitleAndCount(views);
|
||||
|
||||
if (mAccountCount == 0) {
|
||||
if (!isCursorValid() || mCursor.getAccountCount() == 0) {
|
||||
// Hide compose icon & show "touch to configure" text
|
||||
views.setViewVisibility(R.id.widget_compose, View.INVISIBLE);
|
||||
views.setViewVisibility(R.id.message_list, View.GONE);
|
||||
|
@ -566,79 +405,78 @@ public class EmailWidget implements RemoteViewsService.RemoteViewsFactory {
|
|||
@Override
|
||||
public RemoteViews getViewAt(int position) {
|
||||
// Use the cursor to set up the widget
|
||||
synchronized (mCursorLock) {
|
||||
if (mCursor == null || mCursor.isClosed() || !mCursor.moveToPosition(position)) {
|
||||
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;
|
||||
if (!isCursorValid() || !mCursor.moveToPosition(position)) {
|
||||
return getLoadingView();
|
||||
}
|
||||
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
|
||||
public int getCount() {
|
||||
if (mCursor == null) return 0;
|
||||
if (!isCursorValid()) return 0;
|
||||
return Math.min(mCursor.getCount(), MAX_MESSAGE_LIST_COUNT);
|
||||
}
|
||||
|
||||
|
@ -671,7 +509,7 @@ public class EmailWidget implements RemoteViewsService.RemoteViewsFactory {
|
|||
|
||||
public void onDeleted() {
|
||||
if (mLoader != null) {
|
||||
mLoader.stopLoading();
|
||||
mLoader.reset();
|
||||
}
|
||||
WidgetManager.getInstance().remove(mWidgetId);
|
||||
}
|
||||
|
@ -679,7 +517,7 @@ public class EmailWidget implements RemoteViewsService.RemoteViewsFactory {
|
|||
@Override
|
||||
public void onDestroy() {
|
||||
if (mLoader != null) {
|
||||
mLoader.stopLoading();
|
||||
mLoader.reset();
|
||||
}
|
||||
WidgetManager.getInstance().remove(mWidgetId);
|
||||
}
|
||||
|
@ -688,60 +526,46 @@ public class EmailWidget implements RemoteViewsService.RemoteViewsFactory {
|
|||
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.
|
||||
*/
|
||||
/* package */ void switchView() {
|
||||
switchView(false);
|
||||
}
|
||||
|
||||
private WidgetViewSwitcher switchView(boolean disableLoadAfterSwitchForTest) {
|
||||
WidgetViewSwitcher switcher = new WidgetViewSwitcher(this, disableLoadAfterSwitchForTest);
|
||||
switcher.execute();
|
||||
return switcher;
|
||||
new WidgetUpdater(true).execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
WidgetViewSwitcher switcher = switchView(true);
|
||||
try {
|
||||
switcher.get();
|
||||
} catch (InterruptedException e) {
|
||||
Assert.fail();
|
||||
} catch (ExecutionException e) {
|
||||
Assert.fail();
|
||||
}
|
||||
}
|
||||
private class WidgetUpdater extends AsyncTask<Void, Void, WidgetView> {
|
||||
private final WidgetView mCurrentView;
|
||||
private final boolean mSwitchToNextView;
|
||||
|
||||
/**
|
||||
* Utility class to handle switching widget views; in the background, we access the database
|
||||
* to determine account status, etc. In the foreground, we start up the Loader with new
|
||||
* 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;
|
||||
public WidgetUpdater(boolean switchToNextView) {
|
||||
mCurrentView = mWidgetView;
|
||||
mSwitchToNextView = switchToNextView;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
mWidget.switchToNextView();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void param) {
|
||||
if (isCancelled()) {
|
||||
return;
|
||||
protected WidgetView doInBackground(Void... params) {
|
||||
if (mSwitchToNextView || !mCurrentView.isValid(mContext)) {
|
||||
return mCurrentView.getNext(mContext);
|
||||
} else {
|
||||
return mCurrentView; // Reload the same view.
|
||||
}
|
||||
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() {
|
||||
for (EmailWidget widget: mWidgets.values()) {
|
||||
// Anything could have changed; update widget & validate the current view
|
||||
widget.updateWidget(true);
|
||||
widget.validateAndUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void getOrCreateWidgets(Context context, int[] 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);
|
||||
}
|
||||
widget = new EmailWidget(context, widgetId);
|
||||
widget.init();
|
||||
WidgetManager.getInstance().put(widgetId, widget);
|
||||
widget.start();
|
||||
}
|
||||
return widget;
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ public class WidgetManager {
|
|||
int n = 0;
|
||||
for (EmailWidget widget : mWidgets.values()) {
|
||||
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.utility.Utility;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.test.MoreAsserts;
|
||||
|
@ -72,6 +73,14 @@ public class ProviderTestUtils extends Assert {
|
|||
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
|
||||
*/
|
||||
|
|
|
@ -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