MessageList.java revision ded3c915d88a5ee2d143b75cbf5718dae92a2f1c
1/* 2 * Copyright (C) 2009 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.Controller; 20import com.android.email.R; 21import com.android.email.Utility; 22import com.android.email.activity.setup.AccountSettings; 23import com.android.email.mail.MessagingException; 24import com.android.email.provider.EmailContent; 25import com.android.email.provider.EmailContent.MessageColumns; 26 27import android.app.ListActivity; 28import android.content.ContentUris; 29import android.content.ContentValues; 30import android.content.Context; 31import android.content.Intent; 32import android.content.res.Resources; 33import android.database.Cursor; 34import android.graphics.drawable.Drawable; 35import android.net.Uri; 36import android.os.AsyncTask; 37import android.os.Bundle; 38import android.os.Handler; 39import android.view.ContextMenu; 40import android.view.LayoutInflater; 41import android.view.Menu; 42import android.view.MenuItem; 43import android.view.View; 44import android.view.ViewGroup; 45import android.view.Window; 46import android.view.ContextMenu.ContextMenuInfo; 47import android.view.View.OnClickListener; 48import android.view.animation.AnimationUtils; 49import android.widget.AdapterView; 50import android.widget.CursorAdapter; 51import android.widget.ImageView; 52import android.widget.ListView; 53import android.widget.TextView; 54import android.widget.Toast; 55import android.widget.AdapterView.OnItemClickListener; 56 57import java.util.Date; 58import java.util.HashSet; 59import java.util.Set; 60 61public class MessageList extends ListActivity implements OnItemClickListener, OnClickListener { 62 63 // Intent extras (internal to this activity) 64 private static final String EXTRA_ACCOUNT_ID = "com.android.email.activity._ACCOUNT_ID"; 65 private static final String EXTRA_MAILBOX_TYPE = "com.android.email.activity.MAILBOX_TYPE"; 66 private static final String EXTRA_MAILBOX_ID = "com.android.email.activity.MAILBOX_ID"; 67 private static final String EXTRA_ACCOUNT_NAME = "com.android.email.activity.ACCOUNT_NAME"; 68 private static final String EXTRA_MAILBOX_NAME = "com.android.email.activity.MAILBOX_NAME"; 69 70 // UI support 71 private ListView mListView; 72 private View mMultiSelectPanel; 73 private View mReadUnreadButton; 74 private View mFavoriteButton; 75 private View mDeleteButton; 76 private MessageListAdapter mListAdapter; 77 private MessageListHandler mHandler = new MessageListHandler(); 78 private ControllerResults mControllerCallback = new ControllerResults(); 79 80 // DB access 81 private long mMailboxId; 82 private LoadMessagesTask mLoadMessagesTask; 83 84 /** 85 * Open a specific mailbox. 86 * 87 * TODO This should just shortcut to a more generic version that can accept a list of 88 * accounts/mailboxes (e.g. merged inboxes). 89 * 90 * @param context 91 * @param id mailbox key 92 * @param accountName the account we're viewing 93 * @param mailboxName the mailbox we're viewing 94 */ 95 public static void actionHandleAccount(Context context, long id, 96 String accountName, String mailboxName) { 97 Intent intent = new Intent(context, MessageList.class); 98 intent.putExtra(EXTRA_MAILBOX_ID, id); 99 intent.putExtra(EXTRA_ACCOUNT_NAME, accountName); 100 intent.putExtra(EXTRA_MAILBOX_NAME, mailboxName); 101 context.startActivity(intent); 102 } 103 104 /** 105 * Open a specific mailbox by account & type 106 * 107 * @param context The caller's context (for generating an intent) 108 * @param accountId The account to open 109 * @param mailboxType the type of mailbox to open (e.g. @see EmailContent.Mailbox.TYPE_INBOX) 110 */ 111 public static void actionHandleAccount(Context context, long accountId, int mailboxType) { 112 Intent intent = new Intent(context, MessageList.class); 113 intent.putExtra(EXTRA_ACCOUNT_ID, accountId); 114 intent.putExtra(EXTRA_MAILBOX_TYPE, mailboxType); 115 context.startActivity(intent); 116 } 117 118 /** 119 * Return an intent to open a specific mailbox by account & type. It will also clear 120 * notifications. 121 * 122 * @param context The caller's context (for generating an intent) 123 * @param accountId The account to open 124 * @param mailboxType the type of mailbox to open (e.g. @see EmailContent.Mailbox.TYPE_INBOX) 125 */ 126 public static Intent actionHandleAccountIntent(Context context, long accountId, 127 int mailboxType) { 128 Intent intent = new Intent(context, MessageList.class); 129 intent.putExtra(EXTRA_ACCOUNT_ID, accountId); 130 intent.putExtra(EXTRA_MAILBOX_TYPE, mailboxType); 131 return intent; 132 } 133 134 @Override 135 public void onCreate(Bundle icicle) { 136 super.onCreate(icicle); 137 138 requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); 139 140 setContentView(R.layout.message_list); 141 mListView = getListView(); 142 mMultiSelectPanel = findViewById(R.id.footer_organize); 143 mReadUnreadButton = findViewById(R.id.btn_read_unread); 144 mFavoriteButton = findViewById(R.id.btn_multi_favorite); 145 mDeleteButton = findViewById(R.id.btn_multi_delete); 146 147 mReadUnreadButton.setOnClickListener(this); 148 mFavoriteButton.setOnClickListener(this); 149 mDeleteButton.setOnClickListener(this); 150 151 mListView.setOnItemClickListener(this); 152 mListView.setItemsCanFocus(false); 153 registerForContextMenu(mListView); 154 155 mListAdapter = new MessageListAdapter(this); 156 setListAdapter(mListAdapter); 157 158 // TODO extend this to properly deal with multiple mailboxes, cursor, etc. 159 mMailboxId = getIntent().getLongExtra(EXTRA_MAILBOX_ID, -1); 160 if (mMailboxId == -1) { 161 // Try account/type mode 162 long accountId = getIntent().getLongExtra(EXTRA_ACCOUNT_ID, -1); 163 int mailboxType = getIntent().getIntExtra(EXTRA_MAILBOX_TYPE, -1); 164 Cursor c = null; 165 try { 166 c = getContentResolver().query(EmailContent.Mailbox.CONTENT_URI, 167 EmailContent.Mailbox.CONTENT_PROJECTION, 168 EmailContent.MailboxColumns.ACCOUNT_KEY + "=? AND " + 169 EmailContent.MailboxColumns.TYPE + "=?", 170 new String[] { Long.toString(accountId), Integer.toString(mailboxType) }, 171 null); 172 if (c.moveToFirst()) { 173 mMailboxId = c.getLong(EmailContent.Mailbox.CONTENT_ID_COLUMN); 174 } 175 } finally { 176 if (c != null) c.close(); 177 } 178 179 } 180 181 // TODO set title to "account > mailbox (#unread)" 182 183 mLoadMessagesTask = (LoadMessagesTask) new LoadMessagesTask(mMailboxId).execute(); 184 } 185 186 @Override 187 public void onPause() { 188 super.onPause(); 189 Controller.getInstance(getApplication()).removeResultCallback(mControllerCallback); 190 } 191 192 @Override 193 public void onResume() { 194 super.onResume(); 195 Controller.getInstance(getApplication()).addResultCallback(mControllerCallback); 196 197 // TODO: may need to clear notifications here 198 } 199 200 @Override 201 protected void onDestroy() { 202 super.onDestroy(); 203 204 if (mLoadMessagesTask != null && 205 mLoadMessagesTask.getStatus() != LoadMessagesTask.Status.FINISHED) { 206 mLoadMessagesTask.cancel(true); 207 mLoadMessagesTask = null; 208 } 209 } 210 211 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 212 MessageListItem itemView = (MessageListItem) view; 213 onOpenMessage(id, itemView.mMailboxId); 214 } 215 216 public void onClick(View v) { 217 switch (v.getId()) { 218 case R.id.btn_read_unread: 219 onMultiToggleRead(mListAdapter.getSelectedSet()); 220 break; 221 case R.id.btn_multi_favorite: 222 onMultiToggleFavorite(mListAdapter.getSelectedSet()); 223 break; 224 case R.id.btn_multi_delete: 225 onMultiDelete(mListAdapter.getSelectedSet()); 226 break; 227 } 228 } 229 230 @Override 231 public boolean onCreateOptionsMenu(Menu menu) { 232 super.onCreateOptionsMenu(menu); 233 getMenuInflater().inflate(R.menu.message_list_option, menu); 234 return true; 235 } 236 237 @Override 238 public boolean onOptionsItemSelected(MenuItem item) { 239 switch (item.getItemId()) { 240 case R.id.refresh: 241 onRefresh(); 242 return true; 243 case R.id.accounts: 244 onAccounts(); 245 return true; 246 case R.id.compose: 247 onCompose(); 248 return true; 249 case R.id.account_settings: 250 onEditAccount(); 251 return true; 252 default: 253 return super.onOptionsItemSelected(item); 254 } 255 } 256 257 @Override 258 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { 259 super.onCreateContextMenu(menu, v, menuInfo); 260 AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; 261 MessageListItem itemView = (MessageListItem) info.targetView; 262 263 // TODO: There is no context menu for the outbox 264 // TODO: There is probably a special context menu for the trash 265 266 getMenuInflater().inflate(R.menu.message_list_context, menu); 267 268 // The default menu contains "mark as read". If the message is read, change 269 // the menu text to "mark as unread." 270 if (itemView.mRead) { 271 menu.findItem(R.id.mark_as_read).setTitle(R.string.mark_as_unread_action); 272 } 273 } 274 275 @Override 276 public boolean onContextItemSelected(MenuItem item) { 277 AdapterView.AdapterContextMenuInfo info = 278 (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); 279 MessageListItem itemView = (MessageListItem) info.targetView; 280 281 switch (item.getItemId()) { 282 case R.id.open: 283 onOpenMessage(info.id, itemView.mMailboxId); 284 break; 285 case R.id.delete: 286 onDelete(info.id, itemView.mAccountId); 287 break; 288 case R.id.reply: 289 //onReply(holder); 290 break; 291 case R.id.reply_all: 292 //onReplyAll(holder); 293 break; 294 case R.id.forward: 295 //onForward(holder); 296 break; 297 case R.id.mark_as_read: 298 onToggleRead(info.id, itemView.mRead); 299 break; 300 } 301 return super.onContextItemSelected(item); 302 } 303 304 private void onRefresh() { 305 // TODO: This needs to loop through all open mailboxes (there might be more than one) 306 EmailContent.Mailbox mailbox = 307 EmailContent.Mailbox.restoreMailboxWithId(this, mMailboxId); 308 EmailContent.Account account = 309 EmailContent.Account.restoreAccountWithId(this, mailbox.mAccountKey); 310 mHandler.progress(true); 311 Controller.getInstance(getApplication()).updateMailbox( 312 account, mailbox, mControllerCallback); 313 } 314 315 private void onAccounts() { 316 AccountFolderList.actionShowAccounts(this); 317 finish(); 318 } 319 320 private void onCompose() { 321 // TODO: Select correct account to send from when there are multiple mailboxes 322 EmailContent.Mailbox mailbox = 323 EmailContent.Mailbox.restoreMailboxWithId(this, mMailboxId); 324 MessageCompose.actionCompose(this, mailbox.mAccountKey); 325 } 326 327 private void onEditAccount() { 328 // TODO: Select correct account to edit when there are multiple mailboxes 329 EmailContent.Mailbox mailbox = 330 EmailContent.Mailbox.restoreMailboxWithId(this, mMailboxId); 331 AccountSettings.actionSettings(this, mailbox.mAccountKey); 332 } 333 334 public void onOpenMessage(long messageId, long mailboxId) { 335 EmailContent.Mailbox mailbox = EmailContent.Mailbox.restoreMailboxWithId(this, mailboxId); 336 337 if (mailbox.mType == EmailContent.Mailbox.TYPE_DRAFTS) { 338 // TODO need id-based API for MessageCompose 339 // MessageCompose.actionEditDraft(this, messageId); 340 } else { 341 MessageView.actionView(this, messageId); 342 } 343 } 344 345 private void onDelete(long messageId, long accountId) { 346 Controller.getInstance(getApplication()).deleteMessage(messageId, accountId); 347 Toast.makeText(this, R.string.message_deleted_toast, Toast.LENGTH_SHORT).show(); 348 } 349 350 private void onToggleRead(long messageId, boolean oldRead) { 351 boolean isRead = ! oldRead; 352 353 // TODO this should be a call to the controller, since it may possibly kick off 354 // more than just a DB update. Also, the DB update shouldn't be in the UI thread 355 // as it is here. Also, it needs to update the read/unread count in the mailbox? 356 ContentValues cv = new ContentValues(); 357 cv.put(EmailContent.MessageColumns.FLAG_READ, isRead); 358 Uri uri = ContentUris.withAppendedId( 359 EmailContent.Message.SYNCED_CONTENT_URI, messageId); 360 getContentResolver().update(uri, cv, null, null); 361 } 362 363 /** 364 * Toggles a set read/unread states. Note, the default behavior is "mark unread", so the 365 * sense of the helper methods is "true=unread". 366 * 367 * @param selectedSet The current list of selected items 368 */ 369 private void onMultiToggleRead(Set<Long> selectedSet) { 370 int numChanged = toggleMultiple(selectedSet, new MultiToggleHelper() { 371 372 public boolean getField(long messageId, Cursor c) { 373 return c.getInt(MessageListAdapter.COLUMN_READ) == 0; 374 } 375 376 public boolean setField(long messageId, Cursor c, boolean newValue) { 377 boolean oldValue = getField(messageId, c); 378 if (oldValue != newValue) { 379 onToggleRead(messageId, !oldValue); 380 return true; 381 } 382 return false; 383 } 384 }); 385 } 386 387 /** 388 * Toggles a set of favorites (stars) 389 * 390 * @param selectedSet The current list of selected items 391 */ 392 private void onMultiToggleFavorite(Set<Long> selectedSet) { 393 int numChanged = toggleMultiple(selectedSet, new MultiToggleHelper() { 394 395 public boolean getField(long messageId, Cursor c) { 396 return c.getInt(MessageListAdapter.COLUMN_FAVORITE) != 0; 397 } 398 399 public boolean setField(long messageId, Cursor c, boolean newValue) { 400 boolean oldValue = getField(messageId, c); 401 if (oldValue != newValue) { 402 // Update provider 403 // TODO this should probably be a call to the controller, since it may possibly 404 // kick off more than just a DB update. 405 ContentValues cv = new ContentValues(); 406 cv.put(EmailContent.MessageColumns.FLAG_FAVORITE, newValue); 407 Uri uri = ContentUris.withAppendedId( 408 EmailContent.Message.SYNCED_CONTENT_URI, messageId); 409 MessageList.this.getContentResolver().update(uri, cv, null, null); 410 411 return true; 412 } 413 return false; 414 } 415 }); 416 } 417 418 private void onMultiDelete(Set<Long> selectedSet) { 419 // Clone the set, because deleting is going to thrash things 420 HashSet<Long> cloneSet = new HashSet<Long>(selectedSet); 421 for (Long id : cloneSet) { 422 Controller.getInstance(getApplication()).deleteMessage(id, -1); 423 } 424 // TODO: count messages and show "n messages deleted" 425 Toast.makeText(this, R.string.message_deleted_toast, Toast.LENGTH_SHORT).show(); 426 selectedSet.clear(); 427 showMultiPanel(false); 428 } 429 430 private interface MultiToggleHelper { 431 /** 432 * Return true if the field of interest is "set". If one or more are false, then our 433 * bulk action will be to "set". If all are set, our bulk action will be to "clear". 434 * @param messageId the message id of the current message 435 * @param c the cursor, positioned to the item of interest 436 * @return true if the field at this row is "set" 437 */ 438 public boolean getField(long messageId, Cursor c); 439 440 /** 441 * Set or clear the field of interest. Return true if a change was made. 442 * @param messageId the message id of the current message 443 * @param c the cursor, positioned to the item of interest 444 * @param newValue the new value to be set at this row 445 * @return true if a change was actually made 446 */ 447 public boolean setField(long messageId, Cursor c, boolean newValue); 448 } 449 450 /** 451 * Toggle multiple fields in a message, using the following logic: If one or more fields 452 * are "clear", then "set" them. If all fields are "set", then "clear" them all. 453 * 454 * @param selectedSet the set of messages that are selected 455 * @param helper functions to implement the specific getter & setter 456 * @return the number of messages that were updated 457 */ 458 private int toggleMultiple(Set<Long> selectedSet, MultiToggleHelper helper) { 459 Cursor c = mListAdapter.getCursor(); 460 boolean anyWereFound = false; 461 boolean allWereSet = true; 462 463 c.moveToPosition(-1); 464 while (c.moveToNext()) { 465 long id = c.getInt(MessageListAdapter.COLUMN_ID); 466 if (selectedSet.contains(Long.valueOf(id))) { 467 anyWereFound = true; 468 if (!helper.getField(id, c)) { 469 allWereSet = false; 470 break; 471 } 472 } 473 } 474 475 int numChanged = 0; 476 477 if (anyWereFound) { 478 boolean newValue = !allWereSet; 479 c.moveToPosition(-1); 480 while (c.moveToNext()) { 481 long id = c.getInt(MessageListAdapter.COLUMN_ID); 482 if (selectedSet.contains(Long.valueOf(id))) { 483 if (helper.setField(id, c, newValue)) { 484 ++numChanged; 485 } 486 } 487 } 488 } 489 490 return numChanged; 491 } 492 493 /** 494 * Show or hide the panel of multi-select options 495 */ 496 private void showMultiPanel(boolean show) { 497 if (show && mMultiSelectPanel.getVisibility() != View.VISIBLE) { 498 mMultiSelectPanel.setVisibility(View.VISIBLE); 499 mMultiSelectPanel.startAnimation( 500 AnimationUtils.loadAnimation(this, R.anim.footer_appear)); 501 502 } else if (!show && mMultiSelectPanel.getVisibility() != View.GONE) { 503 mMultiSelectPanel.setVisibility(View.GONE); 504 mMultiSelectPanel.startAnimation( 505 AnimationUtils.loadAnimation(this, R.anim.footer_disappear)); 506 } 507 } 508 509 /** 510 * Async task for loading a single folder out of the UI thread 511 * 512 * TODO: Extend API to support compound select (e.g. merged inbox list) 513 */ 514 private class LoadMessagesTask extends AsyncTask<Void, Void, Cursor> { 515 516 private long mMailboxKey; 517 518 /** 519 * Special constructor to cache some local info 520 */ 521 public LoadMessagesTask(long mailboxKey) { 522 mMailboxKey = mailboxKey; 523 } 524 525 @Override 526 protected Cursor doInBackground(Void... params) { 527 return MessageList.this.managedQuery( 528 EmailContent.Message.CONTENT_URI, 529 MessageList.this.mListAdapter.PROJECTION, 530 EmailContent.MessageColumns.MAILBOX_KEY + "=?", 531 new String[] { 532 String.valueOf(mMailboxKey) 533 }, 534 EmailContent.MessageColumns.TIMESTAMP + " DESC"); 535 } 536 537 @Override 538 protected void onPostExecute(Cursor cursor) { 539 MessageList.this.mListAdapter.changeCursor(cursor); 540 541 // TODO: remove this hack and only update at the right time 542 if (cursor != null && cursor.getCount() == 0) { 543 onRefresh(); 544 } 545 } 546 } 547 548 /** 549 * Handler for UI-thread operations (when called from callbacks or any other threads) 550 */ 551 class MessageListHandler extends Handler { 552 private static final int MSG_PROGRESS = 1; 553 554 @Override 555 public void handleMessage(android.os.Message msg) { 556 switch (msg.what) { 557 case MSG_PROGRESS: 558 setProgressBarIndeterminateVisibility(msg.arg1 != 0); 559 break; 560 default: 561 super.handleMessage(msg); 562 } 563 } 564 565 /** 566 * Call from any thread to start/stop progress indicator(s) 567 * @param progress true to start, false to stop 568 */ 569 public void progress(boolean progress) { 570 android.os.Message msg = android.os.Message.obtain(); 571 msg.what = MSG_PROGRESS; 572 msg.arg1 = progress ? 1 : 0; 573 sendMessage(msg); 574 } 575 } 576 577 /** 578 * Callback for async Controller results. This is all a placeholder until we figure out the 579 * final way to do this. 580 */ 581 private class ControllerResults implements Controller.Result { 582 public void updateMailboxListCallback(MessagingException result, long accountKey) { 583 } 584 585 public void updateMailboxCallback(MessagingException result, long accountKey, 586 long mailboxKey, int totalMessagesInMailbox, int numNewMessages) { 587 mHandler.progress(false); 588 } 589 } 590 591 /** 592 * This class implements the adapter for displaying messages based on cursors. 593 */ 594 /* package */ class MessageListAdapter extends CursorAdapter { 595 596 public static final int COLUMN_ID = 0; 597 public static final int COLUMN_MAILBOX_KEY = 1; 598 public static final int COLUMN_ACCOUNT_KEY = 2; 599 public static final int COLUMN_DISPLAY_NAME = 3; 600 public static final int COLUMN_SUBJECT = 4; 601 public static final int COLUMN_DATE = 5; 602 public static final int COLUMN_READ = 6; 603 public static final int COLUMN_FAVORITE = 7; 604 public static final int COLUMN_ATTACHMENTS = 8; 605 606 public final String[] PROJECTION = new String[] { 607 EmailContent.RECORD_ID, MessageColumns.MAILBOX_KEY, MessageColumns.ACCOUNT_KEY, 608 MessageColumns.DISPLAY_NAME, MessageColumns.SUBJECT, MessageColumns.TIMESTAMP, 609 MessageColumns.FLAG_READ, MessageColumns.FLAG_FAVORITE, MessageColumns.FLAG_ATTACHMENT, 610 }; 611 612 Context mContext; 613 private LayoutInflater mInflater; 614 private Drawable mAttachmentIcon; 615 private Drawable mFavoriteIconOn; 616 private Drawable mFavoriteIconOff; 617 private Drawable mSelectedIconOn; 618 private Drawable mSelectedIconOff; 619 620 private java.text.DateFormat mDateFormat; 621 private java.text.DateFormat mDayFormat; 622 private java.text.DateFormat mTimeFormat; 623 624 private HashSet<Long> mChecked = new HashSet<Long>(); 625 626 public MessageListAdapter(Context context) { 627 super(context, null); 628 mContext = context; 629 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 630 631 Resources resources = context.getResources(); 632 mAttachmentIcon = resources.getDrawable(R.drawable.ic_mms_attachment_small); 633 mFavoriteIconOn = resources.getDrawable(android.R.drawable.star_on); 634 mFavoriteIconOff = resources.getDrawable(android.R.drawable.star_off); 635 mSelectedIconOn = resources.getDrawable(R.drawable.btn_check_buttonless_on); 636 mSelectedIconOff = resources.getDrawable(R.drawable.btn_check_buttonless_off); 637 638 mDateFormat = android.text.format.DateFormat.getDateFormat(context); // short date 639 mDayFormat = android.text.format.DateFormat.getDateFormat(context); // TODO: day 640 mTimeFormat = android.text.format.DateFormat.getTimeFormat(context); // 12/24 time 641 } 642 643 public Set<Long> getSelectedSet() { 644 return mChecked; 645 } 646 647 @Override 648 public void bindView(View view, Context context, Cursor cursor) { 649 // Reset the view (in case it was recycled) and prepare for binding 650 MessageListItem itemView = (MessageListItem) view; 651 itemView.bindViewInit(this, true); 652 653 // Load the public fields in the view (for later use) 654 itemView.mMessageId = cursor.getLong(COLUMN_ID); 655 itemView.mMailboxId = cursor.getLong(COLUMN_MAILBOX_KEY); 656 itemView.mAccountId = cursor.getLong(COLUMN_ACCOUNT_KEY); 657 itemView.mRead = cursor.getInt(COLUMN_READ) != 0; 658 itemView.mFavorite = cursor.getInt(COLUMN_FAVORITE) != 0; 659 itemView.mSelected = mChecked.contains(Long.valueOf(itemView.mMessageId)); 660 661 // Load the UI 662 View chipView = view.findViewById(R.id.chip); 663 chipView.getBackground().setAlpha(itemView.mRead ? 0 : 255); 664 665 TextView fromView = (TextView) view.findViewById(R.id.from); 666 String text = cursor.getString(COLUMN_DISPLAY_NAME); 667 if (text != null) fromView.setText(text); 668 669 boolean hasAttachments = cursor.getInt(COLUMN_ATTACHMENTS) != 0; 670 fromView.setCompoundDrawablesWithIntrinsicBounds(null, null, 671 hasAttachments ? mAttachmentIcon : null, null); 672 673 TextView subjectView = (TextView) view.findViewById(R.id.subject); 674 text = cursor.getString(COLUMN_SUBJECT); 675 if (text != null) subjectView.setText(text); 676 677 // TODO ui spec suggests "time", "day", "date" - implement "day" 678 TextView dateView = (TextView) view.findViewById(R.id.date); 679 long timestamp = cursor.getLong(COLUMN_DATE); 680 Date date = new Date(timestamp); 681 if (Utility.isDateToday(date)) { 682 text = mTimeFormat.format(date); 683 } else { 684 text = mDateFormat.format(date); 685 } 686 dateView.setText(text); 687 688 ImageView selectedView = (ImageView) view.findViewById(R.id.selected); 689 selectedView.setImageDrawable(itemView.mSelected ? mSelectedIconOn : mSelectedIconOff); 690 691 ImageView favoriteView = (ImageView) view.findViewById(R.id.favorite); 692 favoriteView.setImageDrawable(itemView.mFavorite ? mFavoriteIconOn : mFavoriteIconOff); 693 } 694 695 @Override 696 public View newView(Context context, Cursor cursor, ViewGroup parent) { 697 return mInflater.inflate(R.layout.message_list_item, parent, false); 698 } 699 700 /** 701 * This is used as a callback from the list items, to set the selected state 702 * 703 * @param itemView the item being changed 704 * @param newSelected the new value of the selected flag (checkbox state) 705 */ 706 public void updateSelected(MessageListItem itemView, boolean newSelected) { 707 ImageView selectedView = (ImageView) itemView.findViewById(R.id.selected); 708 selectedView.setImageDrawable(newSelected ? mSelectedIconOn : mSelectedIconOff); 709 710 // Set checkbox state in list, and show/hide panel if necessary 711 Long id = Long.valueOf(itemView.mMessageId); 712 if (newSelected) { 713 mChecked.add(id); 714 } else { 715 mChecked.remove(id); 716 } 717 718 MessageList.this.showMultiPanel(mChecked.size() > 0); 719 } 720 721 /** 722 * This is used as a callback from the list items, to set the favorite state 723 * 724 * @param itemView the item being changed 725 * @param newFavorite the new value of the favorite flag (star state) 726 */ 727 public void updateFavorite(MessageListItem itemView, boolean newFavorite) { 728 ImageView favoriteView = (ImageView) itemView.findViewById(R.id.favorite); 729 favoriteView.setImageDrawable(newFavorite ? mFavoriteIconOn : mFavoriteIconOff); 730 731 // Update provider 732 // TODO this should probably be a call to the controller, since it may possibly kick off 733 // more than just a DB update. 734 ContentValues cv = new ContentValues(); 735 cv.put(EmailContent.MessageColumns.FLAG_FAVORITE, newFavorite); 736 Uri uri = ContentUris.withAppendedId( 737 EmailContent.Message.SYNCED_CONTENT_URI, itemView.mMessageId); 738 mContext.getContentResolver().update(uri, cv, null, null); 739 } 740 } 741} 742