MailboxFragmentAdapter.java revision da3d04ea31a8452d3e34003b699a02ea02e3632b
1/* 2 * Copyright (C) 2011 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.FolderProperties; 21import com.android.email.R; 22import com.android.email.data.ClosingMatrixCursor; 23import com.android.email.data.ThrottlingCursorLoader; 24import com.android.emailcommon.Logging; 25import com.android.emailcommon.provider.EmailContent; 26import com.android.emailcommon.provider.EmailContent.Account; 27import com.android.emailcommon.provider.EmailContent.AccountColumns; 28import com.android.emailcommon.provider.EmailContent.MailboxColumns; 29import com.android.emailcommon.provider.EmailContent.Message; 30import com.android.emailcommon.provider.Mailbox; 31import com.android.emailcommon.utility.Utility; 32 33import android.content.Context; 34import android.content.Loader; 35import android.database.Cursor; 36import android.database.CursorWrapper; 37import android.database.MatrixCursor; 38import android.database.MatrixCursor.RowBuilder; 39import android.database.MergeCursor; 40import android.util.Log; 41import android.view.View; 42import android.view.ViewGroup; 43import android.widget.ImageView; 44import android.widget.TextView; 45 46/** 47 * Mailbox cursor adapter for the mailbox list fragment. 48 * 49 * A mailbox cursor may contain one of several different types of data. Currently, this 50 * adapter supports the following views: 51 * 1. The standard inbox, mailbox view 52 * 2. The combined mailbox view 53 * 3. Nested folder navigation 54 * 55 * TODO At a minimum, we should break out the loaders. They have no relation to the view code 56 * and only serve to confuse the user. 57 * TODO Determine if we actually need a separate adapter / view / loader for nested folder 58 * navigation. It's a little convoluted at the moment, but, still manageable. 59 */ 60/*package*/ class MailboxFragmentAdapter extends MailboxesAdapter { 61 private static final String MAILBOX_SELECTION = MailboxColumns.ACCOUNT_KEY + "=?" + 62 " AND " + MailboxColumns.ID + "=?"; 63 64 /** 65 * {@link Cursor} with extra information which is returned by the loader created by 66 * {@link MailboxFragmentAdapter#createMailboxesLoader}. 67 */ 68 public static class CursorWithExtras extends CursorWrapper { 69 /** 70 * The number of mailboxes in the cursor if the cursor contains top-level mailboxes. 71 * Otherwise, the number of *child* mailboxes. 72 */ 73 public final int mChildCount; 74 75 CursorWithExtras(Cursor cursor, int childCount) { 76 super(cursor); 77 mChildCount = childCount; 78 } 79 } 80 81 public MailboxFragmentAdapter(Context context, Callback callback) { 82 super(context, callback); 83 } 84 85 @Override 86 public void bindView(View view, Context context, Cursor cursor) { 87 if (view instanceof MailboxListItem) { 88 bindListItem(view, context, cursor); 89 } else { 90 bindListHeader(view, context, cursor); 91 } 92 } 93 private void bindListHeader(View view, Context context, Cursor cursor) { 94 final TextView nameView = (TextView) view.findViewById(R.id.display_name); 95 nameView.setText(getDisplayName(context, cursor)); 96 } 97 private void bindListItem(View view, Context context, Cursor cursor) { 98 final boolean isAccount = isAccountRow(cursor); 99 final int type = cursor.getInt(COLUMN_TYPE); 100 final long id = cursor.getLong(COLUMN_ID); 101 final long accountId = cursor.getLong(COLUMN_ACCOUNT_ID); 102 final int flags = cursor.getInt(COLUMN_FLAGS); 103 final int rowType = cursor.getInt(COLUMN_ROW_TYPE); 104 final boolean hasVisibleChildren = (flags & Mailbox.FLAG_HAS_CHILDREN) != 0 105 && (flags & Mailbox.FLAG_CHILDREN_VISIBLE) != 0; 106 107 MailboxListItem listItem = (MailboxListItem)view; 108 listItem.mMailboxId = isAccountRow(cursor) ? Mailbox.NO_MAILBOX : id; 109 listItem.mMailboxType = type; 110 listItem.mAccountId = accountId; 111 listItem.mIsValidDropTarget = (id >= 0) 112 && !Utility.arrayContains(Mailbox.INVALID_DROP_TARGETS, type) 113 && (flags & Mailbox.FLAG_ACCEPTS_MOVED_MAIL) != 0; 114 listItem.mIsNavigable = hasVisibleChildren; 115 116 listItem.mAdapter = this; 117 // Set the background depending on whether we're in drag mode, the mailbox is a valid 118 // target, etc. 119 mCallback.onBind(listItem); 120 121 // Set mailbox name 122 final TextView nameView = (TextView) view.findViewById(R.id.mailbox_name); 123 nameView.setText(getDisplayName(context, cursor)); 124 // Set count 125 final int count; 126 switch (getCountTypeForMailboxType(cursor)) { 127 case COUNT_TYPE_UNREAD: 128 count = cursor.getInt(COLUMN_UNREAD_COUNT); 129 break; 130 case COUNT_TYPE_TOTAL: 131 count = cursor.getInt(COLUMN_MESSAGE_COUNT); 132 break; 133 default: // no count 134 count = 0; 135 break; 136 } 137 final TextView countView = (TextView) view.findViewById(R.id.message_count); 138 139 // Set folder icon 140 final ImageView folderIcon = (ImageView) view.findViewById(R.id.folder_icon); 141 folderIcon.setImageDrawable( 142 FolderProperties.getInstance(context).getIcon(type, id, flags)); 143 144 final ImageView mailboxExpandedIcon = 145 (ImageView) view.findViewById(R.id.folder_expanded_icon); 146 switch (rowType) { 147 case ROW_TYPE_SUBMAILBOX: 148 if (hasVisibleChildren) { 149 mailboxExpandedIcon.setVisibility(View.VISIBLE); 150 mailboxExpandedIcon.setImageResource( 151 R.drawable.ic_mailbox_collapsed_holo_light); 152 } else { 153 mailboxExpandedIcon.setVisibility(View.INVISIBLE); 154 mailboxExpandedIcon.setImageDrawable(null); 155 } 156 folderIcon.setVisibility(View.INVISIBLE); 157 break; 158 case ROW_TYPE_CURMAILBOX: 159 mailboxExpandedIcon.setVisibility(View.GONE); 160 mailboxExpandedIcon.setImageDrawable(null); 161 folderIcon.setVisibility(View.GONE); 162 break; 163 case ROW_TYPE_MAILBOX: 164 default: // Includes ROW_TYPE_ACCOUNT 165 if (hasVisibleChildren) { 166 mailboxExpandedIcon.setVisibility(View.VISIBLE); 167 mailboxExpandedIcon.setImageResource( 168 R.drawable.ic_mailbox_collapsed_holo_light); 169 } else { 170 mailboxExpandedIcon.setVisibility(View.GONE); 171 mailboxExpandedIcon.setImageDrawable(null); 172 } 173 folderIcon.setVisibility(View.VISIBLE); 174 break; 175 } 176 177 // If the unread count is zero, not to show countView. 178 if (count > 0) { 179 countView.setVisibility(View.VISIBLE); 180 countView.setText(Integer.toString(count)); 181 } else { 182 countView.setVisibility(View.GONE); 183 } 184 185 final View chipView = view.findViewById(R.id.color_chip); 186 if (isAccount) { 187 chipView.setVisibility(View.VISIBLE); 188 chipView.setBackgroundColor(mResourceHelper.getAccountColor(id)); 189 } else { 190 chipView.setVisibility(View.GONE); 191 } 192 } 193 194 @Override 195 public View newView(Context context, Cursor cursor, ViewGroup parent) { 196 if (cursor.getInt(cursor.getColumnIndex(ROW_TYPE)) == ROW_TYPE_HEADER) { 197 return mInflater.inflate(R.layout.mailbox_list_header, parent, false); 198 } 199 return mInflater.inflate(R.layout.mailbox_list_item, parent, false); 200 } 201 202 203 /** 204 * Returns a cursor loader for the mailboxes of the given account. If <code>parentKey</code> 205 * refers to a valid mailbox ID [e.g. non-zero], restrict the loader to only those mailboxes 206 * contained by this parent mailbox. 207 * 208 * Note the returned loader always returns a {@link CursorWithExtras}. 209 */ 210 public static Loader<Cursor> createMailboxesLoader(Context context, long accountId, 211 long parentMailboxId) { 212 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 213 Log.d(Logging.LOG_TAG, "MailboxFragmentAdapter#CursorWithExtras accountId=" + accountId 214 + " parentMailboxId=" + parentMailboxId); 215 } 216 if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) { 217 throw new IllegalArgumentException(); 218 } 219 return new MailboxFragmentLoader(context, accountId, parentMailboxId); 220 } 221 222 /** 223 * Returns a cursor loader for the combined view. 224 */ 225 public static Loader<Cursor> createCombinedViewLoader(Context context) { 226 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 227 Log.d(Logging.LOG_TAG, "MailboxFragmentAdapter#createCombinedViewLoader"); 228 } 229 return new CombinedMailboxLoader(context); 230 } 231 232 /** 233 * Adds a new row into the given cursor. 234 */ 235 private static void addMailboxRow(MatrixCursor cursor, long mailboxId, String displayName, 236 int mailboxType, int unreadCount, int messageCount, int rowType, int flags, 237 long accountId) { 238 long listId = mailboxId; 239 if (mailboxId < 0) { 240 listId = Long.MAX_VALUE + mailboxId; // IDs for the list view must be positive 241 } 242 RowBuilder row = cursor.newRow(); 243 row.add(listId); 244 row.add(mailboxId); 245 row.add(displayName); 246 row.add(mailboxType); 247 row.add(unreadCount); 248 row.add(messageCount); 249 row.add(rowType); 250 row.add(flags); 251 row.add(accountId); 252 } 253 254 private static void addCombinedMailboxRow(MatrixCursor cursor, long id, int mailboxType, 255 int count, boolean showAlways) { 256 if (id >= 0) { 257 throw new IllegalArgumentException(); // Must be QUERY_ALL_*, which are all negative 258 } 259 if (showAlways || (count > 0)) { 260 addMailboxRow( 261 cursor, id, "", mailboxType, count, count, ROW_TYPE_MAILBOX, Mailbox.FLAG_NONE, 262 Account.ACCOUNT_ID_COMBINED_VIEW); 263 } 264 } 265 266 /** 267 * Loads mailboxes that are the children of a given mailbox ID. 268 * 269 * The returned {@link Cursor} is always a {@link CursorWithExtras}. 270 */ 271 private static class MailboxFragmentLoader extends ThrottlingCursorLoader { 272 private final Context mContext; 273 private final long mAccountId; 274 private final long mParentKey; 275 276 MailboxFragmentLoader(Context context, long accountId, long parentKey) { 277 super(context, Mailbox.CONTENT_URI, 278 (parentKey != Mailbox.NO_MAILBOX) 279 ? SUBMAILBOX_PROJECTION 280 : PROJECTION, 281 MAILBOX_SELECTION_WITH_PARENT, 282 new String[] { Long.toString(accountId), Long.toString(parentKey) }, 283 MAILBOX_ORDER_BY); 284 mContext = context; 285 mAccountId = accountId; 286 mParentKey = parentKey; 287 } 288 289 @Override 290 public void onContentChanged() { 291 if (sEnableUpdate) { 292 super.onContentChanged(); 293 } 294 } 295 296 @Override 297 public Cursor loadInBackground() { 298 boolean parentRemoved = false; 299 300 final Cursor childMailboxCursor = super.loadInBackground(); 301 final Cursor returnCursor; 302 303 final int childCount = childMailboxCursor.getCount(); 304 305 if (mParentKey != Mailbox.NO_MAILBOX) { 306 // If we're not showing the top level mailboxes, add the "parent" mailbox. 307 final Cursor parentCursor = getContext().getContentResolver().query( 308 Mailbox.CONTENT_URI, CURMAILBOX_PROJECTION, MAILBOX_SELECTION, 309 new String[] { Long.toString(mAccountId), Long.toString(mParentKey) }, 310 null); 311 returnCursor = new MergeCursor(new Cursor[] { parentCursor, childMailboxCursor }); 312 } else { 313 // TODO Add per-account starred mailbox support 314 final MatrixCursor starredCursor = new MatrixCursor(MATRIX_PROJECTION); 315 int accountStarredCount = Message.getFavoriteMessageCount(mContext, mAccountId); 316 if (accountStarredCount > 0) { 317 // Only add "Starred", if there is at least one starred message 318 final int totalStarredCount = Message.getFavoriteMessageCount(mContext); 319 addCombinedMailboxRow(starredCursor, Mailbox.QUERY_ALL_FAVORITES, 320 Mailbox.TYPE_MAIL, totalStarredCount, true); 321 } 322 returnCursor = new MergeCursor(new Cursor[] { starredCursor, childMailboxCursor }); 323 } 324 return new CursorWithExtras(Utility.CloseTraceCursorWrapper.get(returnCursor), 325 childCount); 326 } 327 } 328 329 /** 330 * Loader for mailboxes in "Combined view". 331 */ 332 /*package*/ static class CombinedMailboxLoader extends ThrottlingCursorLoader { 333 private static final String[] ACCOUNT_PROJECTION = new String[] { 334 EmailContent.RECORD_ID, AccountColumns.DISPLAY_NAME, 335 }; 336 private static final int COLUMN_ACCOUND_ID = 0; 337 private static final int COLUMN_ACCOUNT_DISPLAY_NAME = 1; 338 339 private final Context mContext; 340 341 public CombinedMailboxLoader(Context context) { 342 super(context, Account.CONTENT_URI, ACCOUNT_PROJECTION, null, null, null); 343 mContext = context; 344 } 345 346 @Override 347 public Cursor loadInBackground() { 348 final Cursor accounts = super.loadInBackground(); 349 350 // Build combined mailbox rows. 351 final MatrixCursor returnCursor = buildCombinedMailboxes(mContext, accounts); 352 353 // Add account rows. 354 accounts.moveToPosition(-1); 355 while (accounts.moveToNext()) { 356 final long accountId = accounts.getLong(COLUMN_ACCOUND_ID); 357 final String accountName = accounts.getString(COLUMN_ACCOUNT_DISPLAY_NAME); 358 final int unreadCount = Mailbox.getUnreadCountByAccountAndMailboxType( 359 mContext, accountId, Mailbox.TYPE_INBOX); 360 addMailboxRow(returnCursor, accountId, accountName, Mailbox.TYPE_NONE, 361 unreadCount, unreadCount, ROW_TYPE_ACCOUNT, Mailbox.FLAG_NONE, 362 accountId); 363 } 364 return Utility.CloseTraceCursorWrapper.get(returnCursor); 365 } 366 367 /*package*/ static MatrixCursor buildCombinedMailboxes(Context context, 368 Cursor innerCursor) { 369 MatrixCursor cursor = new ClosingMatrixCursor(PROJECTION, innerCursor); 370 // Combined inbox -- show unread count 371 addCombinedMailboxRow(cursor, Mailbox.QUERY_ALL_INBOXES, Mailbox.TYPE_INBOX, 372 Mailbox.getUnreadCountByMailboxType(context, Mailbox.TYPE_INBOX), true); 373 374 // Favorite (starred) -- show # of favorites 375 addCombinedMailboxRow(cursor, Mailbox.QUERY_ALL_FAVORITES, Mailbox.TYPE_MAIL, 376 Message.getFavoriteMessageCount(context), false); 377 378 // Drafts -- show # of drafts 379 addCombinedMailboxRow(cursor, Mailbox.QUERY_ALL_DRAFTS, Mailbox.TYPE_DRAFTS, 380 Mailbox.getMessageCountByMailboxType(context, Mailbox.TYPE_DRAFTS), false); 381 382 // Outbox -- # of outstanding messages 383 addCombinedMailboxRow(cursor, Mailbox.QUERY_ALL_OUTBOX, Mailbox.TYPE_OUTBOX, 384 Mailbox.getMessageCountByMailboxType(context, Mailbox.TYPE_OUTBOX), false); 385 386 return cursor; 387 } 388 } 389} 390