MessagesAdapter.java revision f5418f1f93b02e7fab9f15eb201800b65510998e
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.email.activity; 18 19import com.android.email.Email; 20import com.android.email.ResourceHelper; 21import com.android.email.data.ThrottlingCursorLoader; 22import com.android.emailcommon.Logging; 23import com.android.emailcommon.provider.Account; 24import com.android.emailcommon.provider.EmailContent; 25import com.android.emailcommon.provider.EmailContent.Message; 26import com.android.emailcommon.provider.EmailContent.MessageColumns; 27import com.android.emailcommon.provider.Mailbox; 28import com.android.emailcommon.utility.TextUtilities; 29import com.android.emailcommon.utility.Utility; 30 31import android.content.Context; 32import android.content.Loader; 33import android.database.Cursor; 34import android.database.CursorWrapper; 35import android.database.MatrixCursor; 36import android.os.Bundle; 37import android.util.Log; 38import android.view.View; 39import android.view.ViewGroup; 40import android.widget.CursorAdapter; 41 42import java.util.HashSet; 43import java.util.Set; 44 45 46/** 47 * This class implements the adapter for displaying messages based on cursors. 48 */ 49/* package */ class MessagesAdapter extends CursorAdapter { 50 private static final String STATE_CHECKED_ITEMS = 51 "com.android.email.activity.MessagesAdapter.checkedItems"; 52 53 /* package */ static final String[] MESSAGE_PROJECTION = new String[] { 54 EmailContent.RECORD_ID, MessageColumns.MAILBOX_KEY, MessageColumns.ACCOUNT_KEY, 55 MessageColumns.DISPLAY_NAME, MessageColumns.SUBJECT, MessageColumns.TIMESTAMP, 56 MessageColumns.FLAG_READ, MessageColumns.FLAG_FAVORITE, MessageColumns.FLAG_ATTACHMENT, 57 MessageColumns.FLAGS, MessageColumns.SNIPPET 58 }; 59 60 public static final int COLUMN_ID = 0; 61 public static final int COLUMN_MAILBOX_KEY = 1; 62 public static final int COLUMN_ACCOUNT_KEY = 2; 63 public static final int COLUMN_DISPLAY_NAME = 3; 64 public static final int COLUMN_SUBJECT = 4; 65 public static final int COLUMN_DATE = 5; 66 public static final int COLUMN_READ = 6; 67 public static final int COLUMN_FAVORITE = 7; 68 public static final int COLUMN_ATTACHMENTS = 8; 69 public static final int COLUMN_FLAGS = 9; 70 public static final int COLUMN_SNIPPET = 10; 71 72 private final ResourceHelper mResourceHelper; 73 74 /** If true, show color chips. */ 75 private boolean mShowColorChips; 76 77 /** If not null, the query represented by this group of messages */ 78 private String mQuery; 79 80 /** 81 * Set of seleced message IDs. 82 */ 83 private final HashSet<Long> mSelectedSet = new HashSet<Long>(); 84 85 /** 86 * Callback from MessageListAdapter. All methods are called on the UI thread. 87 */ 88 public interface Callback { 89 /** Called when the use starts/unstars a message */ 90 void onAdapterFavoriteChanged(MessageListItem itemView, boolean newFavorite); 91 /** Called when the user selects/unselects a message */ 92 void onAdapterSelectedChanged(MessageListItem itemView, boolean newSelected, 93 int mSelectedCount); 94 } 95 96 private final Callback mCallback; 97 98 /** 99 * The actual return type from the loader. 100 */ 101 public static class CursorWithExtras extends CursorWrapper { 102 /** Whether the mailbox is found. */ 103 public final boolean mIsFound; 104 /** {@link Account} that owns the mailbox. Null for combined mailboxes. */ 105 public final Account mAccount; 106 /** {@link Mailbox} for the loaded mailbox. Null for combined mailboxes. */ 107 public final Mailbox mMailbox; 108 /** {@code true} if the account is an EAS account */ 109 public final boolean mIsEasAccount; 110 /** {@code true} if the loaded mailbox can be refreshed. */ 111 public final boolean mIsRefreshable; 112 /** the number of accounts currently configured. */ 113 public final int mCountTotalAccounts; 114 115 private CursorWithExtras(Cursor cursor, 116 boolean found, Account account, Mailbox mailbox, boolean isEasAccount, 117 boolean isRefreshable, int countTotalAccounts) { 118 super(cursor); 119 mIsFound = found; 120 mAccount = account; 121 mMailbox = mailbox; 122 mIsEasAccount = isEasAccount; 123 mIsRefreshable = isRefreshable; 124 mCountTotalAccounts = countTotalAccounts; 125 } 126 } 127 128 public MessagesAdapter(Context context, Callback callback) { 129 super(context.getApplicationContext(), null, 0 /* no auto requery */); 130 mResourceHelper = ResourceHelper.getInstance(context); 131 mCallback = callback; 132 } 133 134 public void onSaveInstanceState(Bundle outState) { 135 outState.putLongArray(STATE_CHECKED_ITEMS, Utility.toPrimitiveLongArray(getSelectedSet())); 136 } 137 138 public void loadState(Bundle savedInstanceState) { 139 Set<Long> checkedset = getSelectedSet(); 140 checkedset.clear(); 141 for (long l: savedInstanceState.getLongArray(STATE_CHECKED_ITEMS)) { 142 checkedset.add(l); 143 } 144 notifyDataSetChanged(); 145 } 146 147 /** 148 * Set true for combined mailboxes. 149 */ 150 public void setShowColorChips(boolean show) { 151 mShowColorChips = show; 152 } 153 154 public void setQuery(String query) { 155 mQuery = query; 156 } 157 158 public Set<Long> getSelectedSet() { 159 return mSelectedSet; 160 } 161 162 /** 163 * Clear the selection. It's preferable to calling {@link Set#clear()} on 164 * {@link #getSelectedSet()}, because it also notifies observers. 165 */ 166 public void clearSelection() { 167 Set<Long> checkedset = getSelectedSet(); 168 if (checkedset.size() > 0) { 169 checkedset.clear(); 170 notifyDataSetChanged(); 171 } 172 } 173 174 public boolean isSelected(MessageListItem itemView) { 175 return getSelectedSet().contains(itemView.mMessageId); 176 } 177 178 @Override 179 public void bindView(View view, Context context, Cursor cursor) { 180 // Reset the view (in case it was recycled) and prepare for binding 181 MessageListItem itemView = (MessageListItem) view; 182 itemView.bindViewInit(this); 183 184 // Load the public fields in the view (for later use) 185 itemView.mMessageId = cursor.getLong(COLUMN_ID); 186 itemView.mMailboxId = cursor.getLong(COLUMN_MAILBOX_KEY); 187 final long accountId = cursor.getLong(COLUMN_ACCOUNT_KEY); 188 itemView.mAccountId = accountId; 189 itemView.mRead = cursor.getInt(COLUMN_READ) != 0; 190 itemView.mIsFavorite = cursor.getInt(COLUMN_FAVORITE) != 0; 191 itemView.mHasInvite = 192 (cursor.getInt(COLUMN_FLAGS) & Message.FLAG_INCOMING_MEETING_INVITE) != 0; 193 itemView.mHasAttachment = cursor.getInt(COLUMN_ATTACHMENTS) != 0; 194 itemView.mTimestamp = cursor.getLong(COLUMN_DATE); 195 itemView.mSender = cursor.getString(COLUMN_DISPLAY_NAME); 196 itemView.mSnippet = cursor.getString(COLUMN_SNIPPET); 197 itemView.mSubject = cursor.getString(COLUMN_SUBJECT); 198 itemView.mSnippetLineCount = MessageListItem.NEEDS_LAYOUT; 199 itemView.mColorChipPaint = 200 mShowColorChips ? mResourceHelper.getAccountColorPaint(accountId) : null; 201 202 if (mQuery != null && itemView.mSnippet != null) { 203 itemView.mSnippet = 204 TextUtilities.highlightTermsInText(cursor.getString(COLUMN_SNIPPET), mQuery); 205 } 206 } 207 208 @Override 209 public View newView(Context context, Cursor cursor, ViewGroup parent) { 210 MessageListItem item = new MessageListItem(context); 211 item.setVisibility(View.VISIBLE); 212 return item; 213 } 214 215 public void toggleSelected(MessageListItem itemView) { 216 updateSelected(itemView, !isSelected(itemView)); 217 } 218 219 /** 220 * This is used as a callback from the list items, to set the selected state 221 * 222 * <p>Must be called on the UI thread. 223 * 224 * @param itemView the item being changed 225 * @param newSelected the new value of the selected flag (checkbox state) 226 */ 227 private void updateSelected(MessageListItem itemView, boolean newSelected) { 228 if (newSelected) { 229 mSelectedSet.add(itemView.mMessageId); 230 } else { 231 mSelectedSet.remove(itemView.mMessageId); 232 } 233 if (mCallback != null) { 234 mCallback.onAdapterSelectedChanged(itemView, newSelected, mSelectedSet.size()); 235 } 236 } 237 238 /** 239 * This is used as a callback from the list items, to set the favorite state 240 * 241 * <p>Must be called on the UI thread. 242 * 243 * @param itemView the item being changed 244 * @param newFavorite the new value of the favorite flag (star state) 245 */ 246 public void updateFavorite(MessageListItem itemView, boolean newFavorite) { 247 changeFavoriteIcon(itemView, newFavorite); 248 if (mCallback != null) { 249 mCallback.onAdapterFavoriteChanged(itemView, newFavorite); 250 } 251 } 252 253 private void changeFavoriteIcon(MessageListItem view, boolean isFavorite) { 254 view.invalidate(); 255 } 256 257 /** 258 * Creates the loader for {@link MessageListFragment}. 259 * 260 * @return always of {@link CursorWithExtras}. 261 */ 262 public static Loader<Cursor> createLoader(Context context, long mailboxId) { 263 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 264 Log.d(Logging.LOG_TAG, "MessagesAdapter createLoader mailboxId=" + mailboxId); 265 } 266 return new MessagesCursorLoader(context, mailboxId); 267 268 } 269 270 static private class MessagesCursorLoader extends ThrottlingCursorLoader { 271 private final Context mContext; 272 private final long mMailboxId; 273 274 public MessagesCursorLoader(Context context, long mailboxId) { 275 // Initialize with no where clause. We'll set it later. 276 super(context, EmailContent.Message.CONTENT_URI, 277 MESSAGE_PROJECTION, null, null, 278 EmailContent.MessageColumns.TIMESTAMP + " DESC"); 279 mContext = context; 280 mMailboxId = mailboxId; 281 } 282 283 @Override 284 public Cursor loadInBackground() { 285 final Cursor returnCursor; 286 287 // Only perform a load if the selected mailbox can hold messages 288 // box can be null on the combined view where we use negative mailbox ids. 289 final boolean canHaveMessages; 290 if (mMailboxId < 0) { 291 // Combined mailboxes can always have messages. 292 canHaveMessages = true; 293 } else { 294 Mailbox box = Mailbox.restoreMailboxWithId(mContext, mMailboxId); 295 canHaveMessages = (box != null) && (box.mFlags & Mailbox.FLAG_HOLDS_MAIL) != 0; 296 } 297 if (canHaveMessages) { 298 // Build the where cause (which can't be done on the UI thread.) 299 setSelection(Message.buildMessageListSelection(mContext, mMailboxId)); 300 // Then do a query to get the cursor 301 returnCursor = super.loadInBackground(); 302 } else { 303 // return an empty cursor 304 returnCursor = new MatrixCursor(getProjection()); 305 } 306 return loadExtras(returnCursor); 307 } 308 309 private Cursor loadExtras(Cursor baseCursor) { 310 boolean found = false; 311 Account account = null; 312 Mailbox mailbox = null; 313 boolean isEasAccount = false; 314 boolean isRefreshable = false; 315 316 if (mMailboxId < 0) { 317 // Magic mailbox. 318 found = true; 319 } else { 320 mailbox = Mailbox.restoreMailboxWithId(mContext, mMailboxId); 321 if (mailbox != null) { 322 account = Account.restoreAccountWithId(mContext, mailbox.mAccountKey); 323 if (account != null) { 324 found = true; 325 isEasAccount = account.isEasAccount(mContext) ; 326 isRefreshable = Mailbox.isRefreshable(mContext, mMailboxId); 327 } else { // Account removed? 328 mailbox = null; 329 } 330 } 331 } 332 final int countAccounts = EmailContent.count(mContext, Account.CONTENT_URI); 333 return new CursorWithExtras(baseCursor, found, account, mailbox, isEasAccount, 334 isRefreshable, countAccounts); 335 } 336 } 337} 338