MailboxFragmentAdapter.java revision 844bf745044b4564f42a68f8b7d40105c4def294
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 final boolean isAccount = isAccountRow(cursor); 88 final int type = cursor.getInt(COLUMN_TYPE); 89 final long id = cursor.getLong(COLUMN_ID); 90 final long accountId = cursor.getLong(COLUMN_ACCOUNT_ID); 91 final int flags = cursor.getInt(COLUMN_FLAGS); 92 final int rowType = cursor.getInt(COLUMN_ROW_TYPE); 93 final boolean hasVisibleChildren = (flags & Mailbox.FLAG_HAS_CHILDREN) != 0 94 && (flags & Mailbox.FLAG_CHILDREN_VISIBLE) != 0; 95 96 MailboxListItem listItem = (MailboxListItem)view; 97 listItem.mMailboxId = isAccountRow(cursor) ? Mailbox.NO_MAILBOX : id; 98 listItem.mMailboxType = type; 99 listItem.mAccountId = accountId; 100 listItem.mIsValidDropTarget = (id >= 0) 101 && !Utility.arrayContains(Mailbox.INVALID_DROP_TARGETS, type) 102 && (flags & Mailbox.FLAG_ACCEPTS_MOVED_MAIL) != 0; 103 listItem.mIsNavigable = hasVisibleChildren; 104 105 listItem.mAdapter = this; 106 // Set the background depending on whether we're in drag mode, the mailbox is a valid 107 // target, etc. 108 mCallback.onBind(listItem); 109 110 // Set mailbox name 111 final TextView nameView = (TextView) view.findViewById(R.id.mailbox_name); 112 nameView.setText(getDisplayName(context, cursor)); 113 // Set count 114 final int count; 115 switch (getCountTypeForMailboxType(cursor)) { 116 case COUNT_TYPE_UNREAD: 117 count = cursor.getInt(COLUMN_UNREAD_COUNT); 118 break; 119 case COUNT_TYPE_TOTAL: 120 count = cursor.getInt(COLUMN_MESSAGE_COUNT); 121 break; 122 default: // no count 123 count = 0; 124 break; 125 } 126 final TextView countView = (TextView) view.findViewById(R.id.message_count); 127 128 // Set folder icon 129 final ImageView folderIcon = (ImageView) view.findViewById(R.id.folder_icon); 130 folderIcon.setImageDrawable( 131 FolderProperties.getInstance(context).getIcon(type, id, flags)); 132 133 final ImageView mailboxExpandedIcon = 134 (ImageView) view.findViewById(R.id.folder_expanded_icon); 135 switch (rowType) { 136 case ROW_TYPE_SUBMAILBOX: 137 if (hasVisibleChildren) { 138 mailboxExpandedIcon.setVisibility(View.VISIBLE); 139 mailboxExpandedIcon.setImageResource( 140 R.drawable.ic_mailbox_collapsed_holo_light); 141 } else { 142 mailboxExpandedIcon.setVisibility(View.INVISIBLE); 143 mailboxExpandedIcon.setImageDrawable(null); 144 } 145 folderIcon.setVisibility(View.INVISIBLE); 146 break; 147 case ROW_TYPE_CURMAILBOX: 148 mailboxExpandedIcon.setVisibility(View.GONE); 149 mailboxExpandedIcon.setImageDrawable(null); 150 folderIcon.setVisibility(View.GONE); 151 break; 152 case ROW_TYPE_MAILBOX: 153 default: // Includes ROW_TYPE_ACCOUNT 154 if (hasVisibleChildren) { 155 mailboxExpandedIcon.setVisibility(View.VISIBLE); 156 mailboxExpandedIcon.setImageResource( 157 R.drawable.ic_mailbox_collapsed_holo_light); 158 } else { 159 mailboxExpandedIcon.setVisibility(View.GONE); 160 mailboxExpandedIcon.setImageDrawable(null); 161 } 162 folderIcon.setVisibility(View.VISIBLE); 163 break; 164 } 165 166 // If the unread count is zero, not to show countView. 167 if (count > 0) { 168 countView.setVisibility(View.VISIBLE); 169 countView.setText(Integer.toString(count)); 170 } else { 171 countView.setVisibility(View.GONE); 172 } 173 174 final View chipView = view.findViewById(R.id.color_chip); 175 if (isAccount) { 176 chipView.setVisibility(View.VISIBLE); 177 chipView.setBackgroundColor(mResourceHelper.getAccountColor(id)); 178 } else { 179 chipView.setVisibility(View.GONE); 180 } 181 } 182 183 @Override 184 public View newView(Context context, Cursor cursor, ViewGroup parent) { 185 return mInflater.inflate(R.layout.mailbox_list_item, parent, false); 186 } 187 188 /** 189 * Returns a cursor loader for the mailboxes of the given account. If <code>parentKey</code> 190 * refers to a valid mailbox ID [e.g. non-zero], restrict the loader to only those mailboxes 191 * contained by this parent mailbox. 192 * 193 * Note the returned loader always returns a {@link CursorWithExtras}. 194 */ 195 public static Loader<Cursor> createMailboxesLoader(Context context, long accountId, 196 long parentMailboxId) { 197 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 198 Log.d(Logging.LOG_TAG, "MailboxFragmentAdapter#CursorWithExtras accountId=" + accountId 199 + " parentMailboxId=" + parentMailboxId); 200 } 201 if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) { 202 throw new IllegalArgumentException(); 203 } 204 return new MailboxFragmentLoader(context, accountId, parentMailboxId); 205 } 206 207 /** 208 * Returns a cursor loader for the combined view. 209 */ 210 public static Loader<Cursor> createCombinedViewLoader(Context context) { 211 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 212 Log.d(Logging.LOG_TAG, "MailboxFragmentAdapter#createCombinedViewLoader"); 213 } 214 return new CombinedMailboxLoader(context); 215 } 216 217 /** 218 * Adds a new row into the given cursor. 219 */ 220 private static void addMailboxRow(MatrixCursor cursor, long mailboxId, String displayName, 221 int mailboxType, int unreadCount, int messageCount, int rowType, int flags, 222 long accountId) { 223 long listId = mailboxId; 224 if (mailboxId < 0) { 225 listId = Long.MAX_VALUE + mailboxId; // IDs for the list view must be positive 226 } 227 RowBuilder row = cursor.newRow(); 228 row.add(listId); 229 row.add(mailboxId); 230 row.add(displayName); 231 row.add(mailboxType); 232 row.add(unreadCount); 233 row.add(messageCount); 234 row.add(rowType); 235 row.add(flags); 236 row.add(accountId); 237 } 238 239 private static void addCombinedMailboxRow(MatrixCursor cursor, long id, int mailboxType, 240 int count, boolean showAlways) { 241 if (id >= 0) { 242 throw new IllegalArgumentException(); // Must be QUERY_ALL_*, which are all negative 243 } 244 if (showAlways || (count > 0)) { 245 addMailboxRow( 246 cursor, id, "", mailboxType, count, count, ROW_TYPE_MAILBOX, Mailbox.FLAG_NONE, 247 Account.ACCOUNT_ID_COMBINED_VIEW); 248 } 249 } 250 251 /** 252 * Loads mailboxes that are the children of a given mailbox ID. 253 * 254 * The returned {@link Cursor} is always a {@link CursorWithExtras}. 255 */ 256 private static class MailboxFragmentLoader extends ThrottlingCursorLoader { 257 private final Context mContext; 258 private final long mAccountId; 259 private final long mParentKey; 260 261 MailboxFragmentLoader(Context context, long accountId, long parentKey) { 262 super(context, Mailbox.CONTENT_URI, 263 (parentKey != Mailbox.NO_MAILBOX) 264 ? MailboxesAdapter.SUBMAILBOX_PROJECTION 265 : MailboxesAdapter.PROJECTION, 266 MAILBOX_SELECTION_WITH_PARENT, 267 new String[] { Long.toString(accountId), Long.toString(parentKey) }, 268 MAILBOX_ORDER_BY); 269 mContext = context; 270 mAccountId = accountId; 271 mParentKey = parentKey; 272 } 273 274 @Override 275 public void onContentChanged() { 276 if (sEnableUpdate) { 277 super.onContentChanged(); 278 } 279 } 280 281 @Override 282 public Cursor loadInBackground() { 283 boolean parentRemoved = false; 284 285 final Cursor childMailboxCursor = super.loadInBackground(); 286 final Cursor returnCursor; 287 288 final int childCount = childMailboxCursor.getCount(); 289 290 if (mParentKey != Mailbox.NO_MAILBOX) { 291 // If we're not showing the top level mailboxes, add the "parent" mailbox. 292 final Cursor parentCursor = getContext().getContentResolver().query( 293 Mailbox.CONTENT_URI, CURMAILBOX_PROJECTION, MAILBOX_SELECTION, 294 new String[] { Long.toString(mAccountId), Long.toString(mParentKey) }, 295 null); 296 297 returnCursor = new MergeCursor(new Cursor[] { parentCursor, childMailboxCursor }); 298 } else { 299 // Add "Starred", only if the account has at least one starred message. 300 // TODO It's currently "combined starred", but the plan is to make it per-account 301 // starred. 302 final int accountStarredCount 303 = Message.getFavoriteMessageCount(mContext, mAccountId); 304 if (accountStarredCount > 0) { 305 final MatrixCursor starredCursor = new MatrixCursor(getProjection()); 306 final int totalStarredCount = Message.getFavoriteMessageCount(mContext); 307 addCombinedMailboxRow(starredCursor, Mailbox.QUERY_ALL_FAVORITES, 308 Mailbox.TYPE_MAIL, totalStarredCount, true); 309 returnCursor 310 = new MergeCursor(new Cursor[] { starredCursor, childMailboxCursor }); 311 } else { 312 returnCursor = childMailboxCursor; // no starred message 313 } 314 } 315 316 return new CursorWithExtras(Utility.CloseTraceCursorWrapper.get(returnCursor), 317 childCount); 318 } 319 } 320 321 /** 322 * Loader for mailboxes in "Combined view". 323 */ 324 /*package*/ static class CombinedMailboxLoader extends ThrottlingCursorLoader { 325 private static final String[] ACCOUNT_PROJECTION = new String[] { 326 EmailContent.RECORD_ID, AccountColumns.DISPLAY_NAME, 327 }; 328 private static final int COLUMN_ACCOUND_ID = 0; 329 private static final int COLUMN_ACCOUNT_DISPLAY_NAME = 1; 330 331 private final Context mContext; 332 333 public CombinedMailboxLoader(Context context) { 334 super(context, Account.CONTENT_URI, ACCOUNT_PROJECTION, null, null, null); 335 mContext = context; 336 } 337 338 @Override 339 public Cursor loadInBackground() { 340 final Cursor accounts = super.loadInBackground(); 341 342 // Build combined mailbox rows. 343 final MatrixCursor returnCursor = buildCombinedMailboxes(mContext, accounts); 344 345 // Add account rows. 346 accounts.moveToPosition(-1); 347 while (accounts.moveToNext()) { 348 final long accountId = accounts.getLong(COLUMN_ACCOUND_ID); 349 final String accountName = accounts.getString(COLUMN_ACCOUNT_DISPLAY_NAME); 350 final int unreadCount = Mailbox.getUnreadCountByAccountAndMailboxType( 351 mContext, accountId, Mailbox.TYPE_INBOX); 352 addMailboxRow(returnCursor, accountId, accountName, Mailbox.TYPE_NONE, 353 unreadCount, unreadCount, ROW_TYPE_ACCOUNT, Mailbox.FLAG_NONE, 354 accountId); 355 } 356 return Utility.CloseTraceCursorWrapper.get(returnCursor); 357 } 358 359 /*package*/ static MatrixCursor buildCombinedMailboxes(Context context, 360 Cursor innerCursor) { 361 MatrixCursor cursor = new ClosingMatrixCursor(PROJECTION, innerCursor); 362 // Combined inbox -- show unread count 363 addCombinedMailboxRow(cursor, Mailbox.QUERY_ALL_INBOXES, Mailbox.TYPE_INBOX, 364 Mailbox.getUnreadCountByMailboxType(context, Mailbox.TYPE_INBOX), true); 365 366 // Favorite (starred) -- show # of favorites 367 addCombinedMailboxRow(cursor, Mailbox.QUERY_ALL_FAVORITES, Mailbox.TYPE_MAIL, 368 Message.getFavoriteMessageCount(context), false); 369 370 // Drafts -- show # of drafts 371 addCombinedMailboxRow(cursor, Mailbox.QUERY_ALL_DRAFTS, Mailbox.TYPE_DRAFTS, 372 Mailbox.getMessageCountByMailboxType(context, Mailbox.TYPE_DRAFTS), false); 373 374 // Outbox -- # of outstanding messages 375 addCombinedMailboxRow(cursor, Mailbox.QUERY_ALL_OUTBOX, Mailbox.TYPE_OUTBOX, 376 Mailbox.getMessageCountByMailboxType(context, Mailbox.TYPE_OUTBOX), false); 377 378 return cursor; 379 } 380 } 381} 382