Merge "Refactoring widget part 2"

This commit is contained in:
Makoto Onuki 2011-02-23 15:12:22 -08:00 committed by Android (Google) Code Review
commit fc8a65aecf
7 changed files with 713 additions and 469 deletions

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}

View File

@ -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
*/

View File

@ -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));
}
}

View File

@ -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));
}
}