MessageList.java revision 6c21942ec45f561d711b3d74ecca8e62afb735c4
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.Account; 26import com.android.email.provider.EmailContent.AccountColumns; 27import com.android.email.provider.EmailContent.Mailbox; 28import com.android.email.provider.EmailContent.MailboxColumns; 29import com.android.email.provider.EmailContent.MessageColumns; 30import com.android.email.service.MailService; 31 32import android.app.ListActivity; 33import android.app.NotificationManager; 34import android.content.ContentResolver; 35import android.content.ContentUris; 36import android.content.Context; 37import android.content.Intent; 38import android.content.res.Resources; 39import android.database.Cursor; 40import android.graphics.drawable.Drawable; 41import android.net.Uri; 42import android.os.AsyncTask; 43import android.os.Bundle; 44import android.os.Handler; 45import android.util.Log; 46import android.view.ContextMenu; 47import android.view.LayoutInflater; 48import android.view.Menu; 49import android.view.MenuItem; 50import android.view.View; 51import android.view.ViewGroup; 52import android.view.Window; 53import android.view.ContextMenu.ContextMenuInfo; 54import android.view.View.OnClickListener; 55import android.view.animation.AnimationUtils; 56import android.widget.AdapterView; 57import android.widget.Button; 58import android.widget.CursorAdapter; 59import android.widget.ImageView; 60import android.widget.ListView; 61import android.widget.ProgressBar; 62import android.widget.TextView; 63import android.widget.Toast; 64import android.widget.AdapterView.OnItemClickListener; 65 66import java.util.Date; 67import java.util.HashSet; 68import java.util.Set; 69 70public class MessageList extends ListActivity implements OnItemClickListener, OnClickListener { 71 // Intent extras (internal to this activity) 72 private static final String EXTRA_ACCOUNT_ID = "com.android.email.activity._ACCOUNT_ID"; 73 private static final String EXTRA_MAILBOX_TYPE = "com.android.email.activity.MAILBOX_TYPE"; 74 private static final String EXTRA_MAILBOX_ID = "com.android.email.activity.MAILBOX_ID"; 75 76 // UI support 77 private ListView mListView; 78 private View mMultiSelectPanel; 79 private Button mReadUnreadButton; 80 private Button mFavoriteButton; 81 private Button mDeleteButton; 82 private View mListFooterView; 83 private TextView mListFooterText; 84 private View mListFooterProgress; 85 86 private static final int LIST_FOOTER_MODE_NONE = 0; 87 private static final int LIST_FOOTER_MODE_REFRESH = 1; 88 private static final int LIST_FOOTER_MODE_MORE = 2; 89 private static final int LIST_FOOTER_MODE_SEND = 3; 90 private int mListFooterMode; 91 92 private MessageListAdapter mListAdapter; 93 private MessageListHandler mHandler = new MessageListHandler(); 94 private Controller mController = Controller.getInstance(getApplication()); 95 private ControllerResults mControllerCallback = new ControllerResults(); 96 private TextView mLeftTitle; 97 private TextView mRightTitle; 98 private ProgressBar mProgressIcon; 99 100 private static final int[] mColorChipResIds = new int[] { 101 R.drawable.appointment_indicator_leftside_1, 102 R.drawable.appointment_indicator_leftside_2, 103 R.drawable.appointment_indicator_leftside_3, 104 R.drawable.appointment_indicator_leftside_4, 105 R.drawable.appointment_indicator_leftside_5, 106 R.drawable.appointment_indicator_leftside_6, 107 R.drawable.appointment_indicator_leftside_7, 108 R.drawable.appointment_indicator_leftside_8, 109 R.drawable.appointment_indicator_leftside_9, 110 R.drawable.appointment_indicator_leftside_10, 111 R.drawable.appointment_indicator_leftside_11, 112 R.drawable.appointment_indicator_leftside_12, 113 R.drawable.appointment_indicator_leftside_13, 114 R.drawable.appointment_indicator_leftside_14, 115 R.drawable.appointment_indicator_leftside_15, 116 R.drawable.appointment_indicator_leftside_16, 117 R.drawable.appointment_indicator_leftside_17, 118 R.drawable.appointment_indicator_leftside_18, 119 R.drawable.appointment_indicator_leftside_19, 120 R.drawable.appointment_indicator_leftside_20, 121 R.drawable.appointment_indicator_leftside_21, 122 }; 123 124 // DB access 125 private ContentResolver mResolver; 126 private long mMailboxId; 127 private LoadMessagesTask mLoadMessagesTask; 128 private FindMailboxTask mFindMailboxTask; 129 private SetTitleTask mSetTitleTask; 130 private SetFooterTask mSetFooterTask; 131 132 public final static String[] MAILBOX_FIND_INBOX_PROJECTION = new String[] { 133 EmailContent.RECORD_ID, MailboxColumns.TYPE, MailboxColumns.FLAG_VISIBLE 134 }; 135 136 private static final int MAILBOX_NAME_COLUMN_ID = 0; 137 private static final int MAILBOX_NAME_COLUMN_ACCOUNT_KEY = 1; 138 private static final int MAILBOX_NAME_COLUMN_TYPE = 2; 139 private static final String[] MAILBOX_NAME_PROJECTION = new String[] { 140 MailboxColumns.DISPLAY_NAME, MailboxColumns.ACCOUNT_KEY, 141 MailboxColumns.TYPE}; 142 143 private static final int ACCOUNT_DISPLAY_NAME_COLUMN_ID = 0; 144 private static final String[] ACCOUNT_NAME_PROJECTION = new String[] { 145 AccountColumns.DISPLAY_NAME }; 146 147 private static final String ID_SELECTION = EmailContent.RECORD_ID + "=?"; 148 149 /** 150 * Open a specific mailbox. 151 * 152 * TODO This should just shortcut to a more generic version that can accept a list of 153 * accounts/mailboxes (e.g. merged inboxes). 154 * 155 * @param context 156 * @param id mailbox key 157 */ 158 public static void actionHandleMailbox(Context context, long id) { 159 Intent intent = new Intent(context, MessageList.class); 160 intent.putExtra(EXTRA_MAILBOX_ID, id); 161 context.startActivity(intent); 162 } 163 164 /** 165 * Open a specific mailbox by account & type 166 * 167 * @param context The caller's context (for generating an intent) 168 * @param accountId The account to open 169 * @param mailboxType the type of mailbox to open (e.g. @see EmailContent.Mailbox.TYPE_INBOX) 170 */ 171 public static void actionHandleAccount(Context context, long accountId, int mailboxType) { 172 Intent intent = new Intent(context, MessageList.class); 173 intent.putExtra(EXTRA_ACCOUNT_ID, accountId); 174 intent.putExtra(EXTRA_MAILBOX_TYPE, mailboxType); 175 context.startActivity(intent); 176 } 177 178 /** 179 * Return an intent to open a specific mailbox by account & type. It will also clear 180 * notifications. 181 * 182 * @param context The caller's context (for generating an intent) 183 * @param accountId The account to open, or -1 184 * @param mailboxId the ID of the mailbox to open, or -1 185 * @param mailboxType the type of mailbox to open (e.g. @see Mailbox.TYPE_INBOX) or -1 186 */ 187 public static Intent actionHandleAccountIntent(Context context, long accountId, 188 long mailboxId, int mailboxType) { 189 Intent intent = new Intent(context, MessageList.class); 190 intent.putExtra(EXTRA_ACCOUNT_ID, accountId); 191 intent.putExtra(EXTRA_MAILBOX_ID, mailboxId); 192 intent.putExtra(EXTRA_MAILBOX_TYPE, mailboxType); 193 return intent; 194 } 195 196 /** 197 * Used for generating lightweight (Uri-only) intents. 198 * 199 * @param context Calling context for building the intent 200 * @param accountId The account of interest 201 * @param mailboxType The folder name to open (typically Mailbox.TYPE_INBOX) 202 * @return an Intent which can be used to view that account 203 */ 204 public static Intent actionHandleAccountUriIntent(Context context, long accountId, 205 int mailboxType) { 206 Intent i = actionHandleAccountIntent(context, accountId, -1, mailboxType); 207 i.removeExtra(EXTRA_ACCOUNT_ID); 208 Uri uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId); 209 i.setData(uri); 210 return i; 211 } 212 213 @Override 214 public void onCreate(Bundle icicle) { 215 super.onCreate(icicle); 216 217 requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); 218 setContentView(R.layout.message_list); 219 getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, 220 R.layout.list_title); 221 222 mListView = getListView(); 223 mMultiSelectPanel = findViewById(R.id.footer_organize); 224 mReadUnreadButton = (Button) findViewById(R.id.btn_read_unread); 225 mFavoriteButton = (Button) findViewById(R.id.btn_multi_favorite); 226 mDeleteButton = (Button) findViewById(R.id.btn_multi_delete); 227 228 mLeftTitle = (TextView) findViewById(R.id.title_left_text); 229 mRightTitle = (TextView) findViewById(R.id.title_right_text); 230 mProgressIcon = (ProgressBar) findViewById(R.id.title_progress_icon); 231 232 mReadUnreadButton.setOnClickListener(this); 233 mFavoriteButton.setOnClickListener(this); 234 mDeleteButton.setOnClickListener(this); 235 236 mListView.setOnItemClickListener(this); 237 mListView.setItemsCanFocus(false); 238 registerForContextMenu(mListView); 239 240 mListAdapter = new MessageListAdapter(this); 241 setListAdapter(mListAdapter); 242 243 mResolver = getContentResolver(); 244 245 // TODO extend this to properly deal with multiple mailboxes, cursor, etc. 246 247 // Select 'by id' or 'by type' or 'by uri' mode and launch appropriate queries 248 249 mMailboxId = getIntent().getLongExtra(EXTRA_MAILBOX_ID, -1); 250 if (mMailboxId != -1) { 251 // Specific mailbox ID was provided - go directly to it 252 mSetTitleTask = new SetTitleTask(mMailboxId); 253 mSetTitleTask.execute(); 254 mLoadMessagesTask = new LoadMessagesTask(mMailboxId, -1); 255 mLoadMessagesTask.execute(); 256 addFooterView(mMailboxId, -1, -1); 257 } else { 258 long accountId = -1; 259 int mailboxType = getIntent().getIntExtra(EXTRA_MAILBOX_TYPE, Mailbox.TYPE_INBOX); 260 Uri uri = getIntent().getData(); 261 if (uri != null 262 && "content".equals(uri.getScheme()) 263 && EmailContent.AUTHORITY.equals(uri.getAuthority())) { 264 // A content URI was provided - try to look up the account 265 String accountIdString = uri.getPathSegments().get(1); 266 if (accountIdString != null) { 267 accountId = Long.parseLong(accountIdString); 268 } 269 mFindMailboxTask = new FindMailboxTask(accountId, mailboxType, false); 270 mFindMailboxTask.execute(); 271 } else { 272 // Go by account id + type 273 accountId = getIntent().getLongExtra(EXTRA_ACCOUNT_ID, -1); 274 mFindMailboxTask = new FindMailboxTask(accountId, mailboxType, true); 275 mFindMailboxTask.execute(); 276 } 277 addFooterView(-1, accountId, mailboxType); 278 } 279 280 // TODO set title to "account > mailbox (#unread)" 281 } 282 283 @Override 284 public void onPause() { 285 super.onPause(); 286 mController.removeResultCallback(mControllerCallback); 287 } 288 289 @Override 290 public void onResume() { 291 super.onResume(); 292 mController.addResultCallback(mControllerCallback); 293 294 // clear notifications here 295 NotificationManager notificationManager = (NotificationManager) 296 getSystemService(Context.NOTIFICATION_SERVICE); 297 notificationManager.cancel(MailService.NEW_MESSAGE_NOTIFICATION_ID); 298 } 299 300 @Override 301 protected void onDestroy() { 302 super.onDestroy(); 303 304 if (mLoadMessagesTask != null && 305 mLoadMessagesTask.getStatus() != LoadMessagesTask.Status.FINISHED) { 306 mLoadMessagesTask.cancel(true); 307 mLoadMessagesTask = null; 308 } 309 if (mFindMailboxTask != null && 310 mFindMailboxTask.getStatus() != FindMailboxTask.Status.FINISHED) { 311 mFindMailboxTask.cancel(true); 312 mFindMailboxTask = null; 313 } 314 if (mSetTitleTask != null && 315 mSetTitleTask.getStatus() != SetTitleTask.Status.FINISHED) { 316 mSetTitleTask.cancel(true); 317 mSetTitleTask = null; 318 } 319 if (mSetFooterTask != null && 320 mSetFooterTask.getStatus() != SetTitleTask.Status.FINISHED) { 321 mSetFooterTask.cancel(true); 322 mSetFooterTask = null; 323 } 324 } 325 326 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 327 if (view != mListFooterView) { 328 MessageListItem itemView = (MessageListItem) view; 329 onOpenMessage(id, itemView.mMailboxId); 330 } else { 331 doFooterClick(); 332 } 333 } 334 335 public void onClick(View v) { 336 switch (v.getId()) { 337 case R.id.btn_read_unread: 338 onMultiToggleRead(mListAdapter.getSelectedSet()); 339 break; 340 case R.id.btn_multi_favorite: 341 onMultiToggleFavorite(mListAdapter.getSelectedSet()); 342 break; 343 case R.id.btn_multi_delete: 344 onMultiDelete(mListAdapter.getSelectedSet()); 345 break; 346 } 347 } 348 349 @Override 350 public boolean onCreateOptionsMenu(Menu menu) { 351 super.onCreateOptionsMenu(menu); 352 if (mMailboxId < 0) { 353 getMenuInflater().inflate(R.menu.message_list_option_smart_folder, menu); 354 } else { 355 getMenuInflater().inflate(R.menu.message_list_option, menu); 356 } 357 return true; 358 } 359 360 @Override 361 public boolean onOptionsItemSelected(MenuItem item) { 362 switch (item.getItemId()) { 363 case R.id.refresh: 364 onRefresh(); 365 return true; 366 case R.id.folders: 367 onFolders(); 368 return true; 369 case R.id.accounts: 370 onAccounts(); 371 return true; 372 case R.id.compose: 373 onCompose(); 374 return true; 375 case R.id.account_settings: 376 onEditAccount(); 377 return true; 378 default: 379 return super.onOptionsItemSelected(item); 380 } 381 } 382 383 @Override 384 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { 385 super.onCreateContextMenu(menu, v, menuInfo); 386 387 AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; 388 // There is no context menu for the list footer 389 if (info.targetView == mListFooterView) { 390 return; 391 } 392 MessageListItem itemView = (MessageListItem) info.targetView; 393 394 Cursor c = (Cursor) mListView.getItemAtPosition(info.position); 395 String messageName = c.getString(MessageListAdapter.COLUMN_SUBJECT); 396 397 menu.setHeaderTitle(messageName); 398 399 // TODO: There is probably a special context menu for the trash 400 Mailbox mailbox = Mailbox.restoreMailboxWithId(this, itemView.mMailboxId); 401 402 switch (mailbox.mType) { 403 case EmailContent.Mailbox.TYPE_DRAFTS: 404 getMenuInflater().inflate(R.menu.message_list_context_drafts, menu); 405 break; 406 case EmailContent.Mailbox.TYPE_OUTBOX: 407 getMenuInflater().inflate(R.menu.message_list_context_outbox, menu); 408 break; 409 case EmailContent.Mailbox.TYPE_TRASH: 410 getMenuInflater().inflate(R.menu.message_list_context_trash, menu); 411 break; 412 default: 413 getMenuInflater().inflate(R.menu.message_list_context, menu); 414 // The default menu contains "mark as read". If the message is read, change 415 // the menu text to "mark as unread." 416 if (itemView.mRead) { 417 menu.findItem(R.id.mark_as_read).setTitle(R.string.mark_as_unread_action); 418 } 419 break; 420 } 421 } 422 423 @Override 424 public boolean onContextItemSelected(MenuItem item) { 425 AdapterView.AdapterContextMenuInfo info = 426 (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); 427 MessageListItem itemView = (MessageListItem) info.targetView; 428 429 switch (item.getItemId()) { 430 case R.id.open: 431 onOpenMessage(info.id, itemView.mMailboxId); 432 break; 433 case R.id.delete: 434 onDelete(info.id, itemView.mAccountId); 435 break; 436 case R.id.reply: 437 onReply(itemView.mMessageId); 438 break; 439 case R.id.reply_all: 440 onReplyAll(itemView.mMessageId); 441 break; 442 case R.id.forward: 443 onForward(itemView.mMessageId); 444 break; 445 case R.id.mark_as_read: 446 onSetMessageRead(info.id, !itemView.mRead); 447 break; 448 } 449 return super.onContextItemSelected(item); 450 } 451 452 private void onRefresh() { 453 // TODO: This needs to loop through all open mailboxes (there might be more than one) 454 // TODO: Should not be reading from DB in UI thread - need a cleaner way to get accountId 455 if (mMailboxId >= 0) { 456 Mailbox mailbox = Mailbox.restoreMailboxWithId(this, mMailboxId); 457 mController.updateMailbox(mailbox.mAccountKey, mMailboxId, mControllerCallback); 458 } 459 } 460 461 private void onFolders() { 462 if (mMailboxId >= 0) { 463 // TODO smaller projection 464 Mailbox mailbox = Mailbox.restoreMailboxWithId(this, mMailboxId); 465 MailboxList.actionHandleAccount(this, mailbox.mAccountKey); 466 finish(); 467 } 468 } 469 470 private void onAccounts() { 471 AccountFolderList.actionShowAccounts(this); 472 finish(); 473 } 474 475 private long lookupAccountIdFromMailboxId(long mailboxId) { 476 // TODO: Select correct account to send from when there are multiple mailboxes 477 // TODO: Should not be reading from DB in UI thread 478 if (mailboxId < 0) { 479 return -1; // no info, default account 480 } 481 EmailContent.Mailbox mailbox = 482 EmailContent.Mailbox.restoreMailboxWithId(this, mailboxId); 483 return mailbox.mAccountKey; 484 } 485 486 private void onCompose() { 487 MessageCompose.actionCompose(this, lookupAccountIdFromMailboxId(mMailboxId)); 488 } 489 490 private void onEditAccount() { 491 AccountSettings.actionSettings(this, lookupAccountIdFromMailboxId(mMailboxId)); 492 } 493 494 private void onOpenMessage(long messageId, long mailboxId) { 495 // TODO: Should not be reading from DB in UI thread 496 EmailContent.Mailbox mailbox = EmailContent.Mailbox.restoreMailboxWithId(this, mailboxId); 497 498 if (mailbox.mType == EmailContent.Mailbox.TYPE_DRAFTS) { 499 MessageCompose.actionEditDraft(this, messageId); 500 } else { 501 MessageView.actionView(this, messageId, mailboxId); 502 } 503 } 504 505 private void onReply(long messageId) { 506 MessageCompose.actionReply(this, messageId, false); 507 } 508 509 private void onReplyAll(long messageId) { 510 MessageCompose.actionReply(this, messageId, true); 511 } 512 513 private void onForward(long messageId) { 514 MessageCompose.actionForward(this, messageId); 515 } 516 517 private void onLoadMoreMessages() { 518 if (mMailboxId >= 0) { 519 mController.loadMoreMessages(mMailboxId, mControllerCallback); 520 } 521 } 522 523 private void onSendPendingMessages() { 524 long accountId = lookupAccountIdFromMailboxId(mMailboxId); 525 mController.sendPendingMessages(accountId, mControllerCallback); 526 } 527 528 private void onDelete(long messageId, long accountId) { 529 mController.deleteMessage(messageId, accountId); 530 Toast.makeText(this, R.string.message_deleted_toast, Toast.LENGTH_SHORT).show(); 531 } 532 533 private void onSetMessageRead(long messageId, boolean newRead) { 534 mController.setMessageRead(messageId, newRead); 535 } 536 537 private void onSetMessageFavorite(long messageId, boolean newFavorite) { 538 mController.setMessageFavorite(messageId, newFavorite); 539 } 540 541 /** 542 * Toggles a set read/unread states. Note, the default behavior is "mark unread", so the 543 * sense of the helper methods is "true=unread". 544 * 545 * @param selectedSet The current list of selected items 546 */ 547 private void onMultiToggleRead(Set<Long> selectedSet) { 548 toggleMultiple(selectedSet, new MultiToggleHelper() { 549 550 public boolean getField(long messageId, Cursor c) { 551 return c.getInt(MessageListAdapter.COLUMN_READ) == 0; 552 } 553 554 public boolean setField(long messageId, Cursor c, boolean newValue) { 555 boolean oldValue = getField(messageId, c); 556 if (oldValue != newValue) { 557 onSetMessageRead(messageId, !newValue); 558 return true; 559 } 560 return false; 561 } 562 }); 563 } 564 565 /** 566 * Toggles a set of favorites (stars) 567 * 568 * @param selectedSet The current list of selected items 569 */ 570 private void onMultiToggleFavorite(Set<Long> selectedSet) { 571 toggleMultiple(selectedSet, new MultiToggleHelper() { 572 573 public boolean getField(long messageId, Cursor c) { 574 return c.getInt(MessageListAdapter.COLUMN_FAVORITE) != 0; 575 } 576 577 public boolean setField(long messageId, Cursor c, boolean newValue) { 578 boolean oldValue = getField(messageId, c); 579 if (oldValue != newValue) { 580 onSetMessageFavorite(messageId, newValue); 581 return true; 582 } 583 return false; 584 } 585 }); 586 } 587 588 private void onMultiDelete(Set<Long> selectedSet) { 589 // Clone the set, because deleting is going to thrash things 590 HashSet<Long> cloneSet = new HashSet<Long>(selectedSet); 591 for (Long id : cloneSet) { 592 mController.deleteMessage(id, -1); 593 } 594 // TODO: count messages and show "n messages deleted" 595 Toast.makeText(this, R.string.message_deleted_toast, Toast.LENGTH_SHORT).show(); 596 selectedSet.clear(); 597 showMultiPanel(false); 598 } 599 600 private interface MultiToggleHelper { 601 /** 602 * Return true if the field of interest is "set". If one or more are false, then our 603 * bulk action will be to "set". If all are set, our bulk action will be to "clear". 604 * @param messageId the message id of the current message 605 * @param c the cursor, positioned to the item of interest 606 * @return true if the field at this row is "set" 607 */ 608 public boolean getField(long messageId, Cursor c); 609 610 /** 611 * Set or clear the field of interest. Return true if a change was made. 612 * @param messageId the message id of the current message 613 * @param c the cursor, positioned to the item of interest 614 * @param newValue the new value to be set at this row 615 * @return true if a change was actually made 616 */ 617 public boolean setField(long messageId, Cursor c, boolean newValue); 618 } 619 620 /** 621 * Toggle multiple fields in a message, using the following logic: If one or more fields 622 * are "clear", then "set" them. If all fields are "set", then "clear" them all. 623 * 624 * @param selectedSet the set of messages that are selected 625 * @param helper functions to implement the specific getter & setter 626 * @return the number of messages that were updated 627 */ 628 private int toggleMultiple(Set<Long> selectedSet, MultiToggleHelper helper) { 629 Cursor c = mListAdapter.getCursor(); 630 boolean anyWereFound = false; 631 boolean allWereSet = true; 632 633 c.moveToPosition(-1); 634 while (c.moveToNext()) { 635 long id = c.getInt(MessageListAdapter.COLUMN_ID); 636 if (selectedSet.contains(Long.valueOf(id))) { 637 anyWereFound = true; 638 if (!helper.getField(id, c)) { 639 allWereSet = false; 640 break; 641 } 642 } 643 } 644 645 int numChanged = 0; 646 647 if (anyWereFound) { 648 boolean newValue = !allWereSet; 649 c.moveToPosition(-1); 650 while (c.moveToNext()) { 651 long id = c.getInt(MessageListAdapter.COLUMN_ID); 652 if (selectedSet.contains(Long.valueOf(id))) { 653 if (helper.setField(id, c, newValue)) { 654 ++numChanged; 655 } 656 } 657 } 658 } 659 660 return numChanged; 661 } 662 663 /** 664 * Test selected messages for showing appropriate labels 665 * @param selectedSet 666 * @param column_id 667 * @param defaultflag 668 * @return true when the specified flagged message is selected 669 */ 670 private boolean testMultiple(Set<Long> selectedSet, int column_id, boolean defaultflag) { 671 Cursor c = mListAdapter.getCursor(); 672 c.moveToPosition(-1); 673 while (c.moveToNext()) { 674 long id = c.getInt(MessageListAdapter.COLUMN_ID); 675 if (selectedSet.contains(Long.valueOf(id))) { 676 if (c.getInt(column_id) == (defaultflag? 1 : 0)) { 677 return true; 678 } 679 } 680 } 681 return false; 682 } 683 684 private void updateFooterButtonNames () { 685 // Show "unread_action" when one or more read messages are selected. 686 if (testMultiple(mListAdapter.getSelectedSet(), MessageListAdapter.COLUMN_READ, true)) { 687 mReadUnreadButton.setText(R.string.unread_action); 688 } else { 689 mReadUnreadButton.setText(R.string.read_action); 690 } 691 // Show "set_star_action" when one or more un-starred messages are selected. 692 if (testMultiple(mListAdapter.getSelectedSet(), 693 MessageListAdapter.COLUMN_FAVORITE, false)) { 694 mFavoriteButton.setText(R.string.set_star_action); 695 } else { 696 mFavoriteButton.setText(R.string.remove_star_action); 697 } 698 } 699 700 /** 701 * Show or hide the panel of multi-select options 702 */ 703 private void showMultiPanel(boolean show) { 704 if (show && mMultiSelectPanel.getVisibility() != View.VISIBLE) { 705 mMultiSelectPanel.setVisibility(View.VISIBLE); 706 mMultiSelectPanel.startAnimation( 707 AnimationUtils.loadAnimation(this, R.anim.footer_appear)); 708 } else if (!show && mMultiSelectPanel.getVisibility() != View.GONE) { 709 mMultiSelectPanel.setVisibility(View.GONE); 710 mMultiSelectPanel.startAnimation( 711 AnimationUtils.loadAnimation(this, R.anim.footer_disappear)); 712 } 713 if (show) { 714 updateFooterButtonNames(); 715 } 716 } 717 718 /** 719 * Add the fixed footer view if appropriate (not always - not all accounts & mailboxes). 720 * 721 * Here are some rules (finish this list): 722 * 723 * Any merged box (except send): refresh 724 * Any push-mode account: refresh 725 * Any non-push-mode account: load more 726 * Any outbox (send again): 727 * 728 * @param mailboxId the ID of the mailbox 729 */ 730 private void addFooterView(long mailboxId, long accountId, int mailboxType) { 731 // first, look for shortcuts that don't need us to spin up a DB access task 732 if (mailboxId == Mailbox.QUERY_ALL_INBOXES 733 || mailboxId == Mailbox.QUERY_ALL_UNREAD 734 || mailboxId == Mailbox.QUERY_ALL_FAVORITES 735 || mailboxId == Mailbox.QUERY_ALL_DRAFTS) { 736 finishFooterView(LIST_FOOTER_MODE_REFRESH); 737 return; 738 } 739 if (mailboxId == Mailbox.QUERY_ALL_OUTBOX || mailboxType == Mailbox.TYPE_OUTBOX) { 740 finishFooterView(LIST_FOOTER_MODE_SEND); 741 return; 742 } 743 744 // We don't know enough to select the footer command type (yet), so we'll 745 // launch an async task to do the remaining lookups and decide what to do 746 mSetFooterTask = new SetFooterTask(); 747 mSetFooterTask.execute(mailboxId, accountId); 748 } 749 750 private final static String[] MAILBOX_ACCOUNT_AND_TYPE_PROJECTION = 751 new String[] { MailboxColumns.ACCOUNT_KEY, MailboxColumns.TYPE }; 752 753 private class SetFooterTask extends AsyncTask<Long, Void, Integer> { 754 /** 755 * There are two operational modes here, requiring different lookup. 756 * mailboxIs != -1: A specific mailbox - check its type, then look up its account 757 * accountId != -1: A specific account - look up the account 758 */ 759 @Override 760 protected Integer doInBackground(Long... params) { 761 long mailboxId = params[0]; 762 long accountId = params[1]; 763 int mailboxType = -1; 764 if (mailboxId != -1) { 765 try { 766 Uri uri = ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId); 767 Cursor c = mResolver.query(uri, MAILBOX_ACCOUNT_AND_TYPE_PROJECTION, 768 null, null, null); 769 if (c.moveToFirst()) { 770 try { 771 accountId = c.getLong(0); 772 mailboxType = c.getInt(1); 773 } finally { 774 c.close(); 775 } 776 } 777 } catch (IllegalArgumentException iae) { 778 // can't do any more here 779 return LIST_FOOTER_MODE_NONE; 780 } 781 } 782 if (mailboxType == Mailbox.TYPE_OUTBOX) { 783 return LIST_FOOTER_MODE_SEND; 784 } 785 if (accountId != -1) { 786 // This is inefficient but the best fix is not here but in isMessagingController 787 Account account = Account.restoreAccountWithId(MessageList.this, accountId); 788 if (account != null) { 789 if (MessageList.this.mController.isMessagingController(account)) { 790 return LIST_FOOTER_MODE_MORE; // IMAP or POP 791 } else { 792 return LIST_FOOTER_MODE_REFRESH; // EAS 793 } 794 } 795 } 796 return LIST_FOOTER_MODE_NONE; 797 } 798 799 @Override 800 protected void onPostExecute(Integer listFooterMode) { 801 finishFooterView(listFooterMode); 802 } 803 } 804 805 /** 806 * Add the fixed footer view as specified, and set up the test as well. 807 * 808 * @param listFooterMode the footer mode we've determined should be used for this list 809 */ 810 private void finishFooterView(int listFooterMode) { 811 mListFooterMode = listFooterMode; 812 if (mListFooterMode != LIST_FOOTER_MODE_NONE) { 813 mListFooterView = ((LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE)) 814 .inflate(R.layout.message_list_item_footer, mListView, false); 815 mList.addFooterView(mListFooterView); 816 setListAdapter(mListAdapter); 817 818 mListFooterProgress = mListFooterView.findViewById(R.id.progress); 819 mListFooterText = (TextView) mListFooterView.findViewById(R.id.main_text); 820 setListFooterText(false); 821 } 822 } 823 824 /** 825 * Set the list footer text based on mode and "active" status 826 */ 827 private void setListFooterText(boolean active) { 828 if (mListFooterMode != LIST_FOOTER_MODE_NONE) { 829 int footerTextId = 0; 830 switch (mListFooterMode) { 831 case LIST_FOOTER_MODE_REFRESH: 832 footerTextId = active ? R.string.status_loading_more 833 : R.string.refresh_action; 834 break; 835 case LIST_FOOTER_MODE_MORE: 836 footerTextId = active ? R.string.status_loading_more 837 : R.string.message_list_load_more_messages_action; 838 break; 839 case LIST_FOOTER_MODE_SEND: 840 footerTextId = active ? R.string.status_sending_messages 841 : R.string.message_list_send_pending_messages_action; 842 break; 843 } 844 mListFooterText.setText(footerTextId); 845 } 846 } 847 848 /** 849 * Handle a click in the list footer, which changes meaning depending on what we're looking at. 850 */ 851 private void doFooterClick() { 852 switch (mListFooterMode) { 853 case LIST_FOOTER_MODE_NONE: // should never happen 854 break; 855 case LIST_FOOTER_MODE_REFRESH: 856 onRefresh(); 857 break; 858 case LIST_FOOTER_MODE_MORE: 859 onLoadMoreMessages(); 860 break; 861 case LIST_FOOTER_MODE_SEND: 862 onSendPendingMessages(); 863 break; 864 } 865 } 866 867 /** 868 * Async task for finding a single mailbox by type (possibly even going to the network). 869 * 870 * This is much too complex, as implemented. It uses this AsyncTask to check for a mailbox, 871 * then (if not found) a Controller call to refresh mailboxes from the server, and a handler 872 * to relaunch this task (a 2nd time) to read the results of the network refresh. The core 873 * problem is that we have two different non-UI-thread jobs (reading DB and reading network) 874 * and two different paradigms for dealing with them. Some unification would be needed here 875 * to make this cleaner. 876 * 877 * TODO: If this problem spreads to other operations, find a cleaner way to handle it. 878 */ 879 private class FindMailboxTask extends AsyncTask<Void, Void, Long> { 880 881 private long mAccountId; 882 private int mMailboxType; 883 private boolean mOkToRecurse; 884 885 /** 886 * Special constructor to cache some local info 887 */ 888 public FindMailboxTask(long accountId, int mailboxType, boolean okToRecurse) { 889 mAccountId = accountId; 890 mMailboxType = mailboxType; 891 mOkToRecurse = okToRecurse; 892 } 893 894 @Override 895 protected Long doInBackground(Void... params) { 896 // See if we can find the requested mailbox in the DB. 897 long mailboxId = Mailbox.findMailboxOfType(MessageList.this, mAccountId, mMailboxType); 898 if (mailboxId == -1 && mOkToRecurse) { 899 // Not found - launch network lookup 900 mControllerCallback.mWaitForMailboxType = mMailboxType; 901 mController.updateMailboxList(mAccountId, mControllerCallback); 902 } 903 return mailboxId; 904 } 905 906 @Override 907 protected void onPostExecute(Long mailboxId) { 908 if (mailboxId != -1) { 909 mMailboxId = mailboxId; 910 mSetTitleTask = new SetTitleTask(mMailboxId); 911 mSetTitleTask.execute(); 912 mLoadMessagesTask = new LoadMessagesTask(mMailboxId, mAccountId); 913 mLoadMessagesTask.execute(); 914 } 915 } 916 } 917 918 /** 919 * Async task for loading a single folder out of the UI thread 920 * 921 * The code here (for merged boxes) is a placeholder/hack and should be replaced. Some 922 * specific notes: 923 * TODO: Move the double query into a specialized URI that returns all inbox messages 924 * and do the dirty work in raw SQL in the provider. 925 * TODO: Generalize the query generation so we can reuse it in MessageView (for next/prev) 926 */ 927 private class LoadMessagesTask extends AsyncTask<Void, Void, Cursor> { 928 929 private long mMailboxKey; 930 private long mAccountKey; 931 932 /** 933 * Special constructor to cache some local info 934 */ 935 public LoadMessagesTask(long mailboxKey, long accountKey) { 936 mMailboxKey = mailboxKey; 937 mAccountKey = accountKey; 938 } 939 940 @Override 941 protected Cursor doInBackground(Void... params) { 942 String selection = 943 Utility.buildMailboxIdSelection(MessageList.this.mResolver, mMailboxKey); 944 Cursor c = MessageList.this.managedQuery( 945 EmailContent.Message.CONTENT_URI, 946 MessageList.this.mListAdapter.PROJECTION, 947 selection, null, 948 EmailContent.MessageColumns.TIMESTAMP + " DESC"); 949 return c; 950 } 951 952 @Override 953 protected void onPostExecute(Cursor cursor) { 954 if (cursor.isClosed()) { 955 return; 956 } 957 MessageList.this.mListAdapter.changeCursor(cursor); 958 959 // TODO: remove this hack and only update at the right time 960 if (cursor != null && cursor.getCount() == 0) { 961 onRefresh(); 962 } 963 964 // Reset the "new messages" count in the service, since we're seeing them now 965 if (mMailboxKey == Mailbox.QUERY_ALL_INBOXES) { 966 MailService.resetNewMessageCount(MessageList.this, -1); 967 } else if (mMailboxKey >= 0 && mAccountKey != -1) { 968 MailService.resetNewMessageCount(MessageList.this, mAccountKey); 969 } 970 } 971 } 972 973 private class SetTitleTask extends AsyncTask<Void, Void, String[]> { 974 975 private long mMailboxKey; 976 977 public SetTitleTask(long mailboxKey) { 978 mMailboxKey = mailboxKey; 979 } 980 981 @Override 982 protected String[] doInBackground(Void... params) { 983 // Check special Mailboxes 984 if (mMailboxKey == Mailbox.QUERY_ALL_INBOXES) { 985 return new String[] {null, 986 getString(R.string.account_folder_list_summary_inbox)}; 987 } else if (mMailboxKey == Mailbox.QUERY_ALL_FAVORITES) { 988 return new String[] {null, 989 getString(R.string.account_folder_list_summary_favorite)}; 990 } else if (mMailboxKey == Mailbox.QUERY_ALL_DRAFTS) { 991 return new String[] {null, 992 getString(R.string.account_folder_list_summary_drafts)}; 993 } else if (mMailboxKey == Mailbox.QUERY_ALL_OUTBOX) { 994 return new String[] {null, 995 getString(R.string.account_folder_list_summary_outbox)}; 996 } 997 String accountName = null; 998 String mailboxName = null; 999 String accountKey = null; 1000 Cursor c = MessageList.this.mResolver.query(Mailbox.CONTENT_URI, 1001 MAILBOX_NAME_PROJECTION, ID_SELECTION, 1002 new String[] { Long.toString(mMailboxKey) }, null); 1003 try { 1004 if (c.moveToFirst()) { 1005 mailboxName = Utility.FolderProperties.getInstance(MessageList.this) 1006 .getDisplayName(c.getInt(MAILBOX_NAME_COLUMN_TYPE)); 1007 if (mailboxName == null) { 1008 mailboxName = c.getString(MAILBOX_NAME_COLUMN_ID); 1009 } 1010 accountKey = c.getString(MAILBOX_NAME_COLUMN_ACCOUNT_KEY); 1011 } 1012 } finally { 1013 c.close(); 1014 } 1015 if (accountKey != null) { 1016 c = MessageList.this.mResolver.query(Account.CONTENT_URI, 1017 ACCOUNT_NAME_PROJECTION, ID_SELECTION, new String[] { accountKey }, 1018 null); 1019 try { 1020 if (c.moveToFirst()) { 1021 accountName = c.getString(ACCOUNT_DISPLAY_NAME_COLUMN_ID); 1022 } 1023 } finally { 1024 c.close(); 1025 } 1026 } 1027 return new String[] {accountName, mailboxName}; 1028 } 1029 1030 @Override 1031 protected void onPostExecute(String[] names) { 1032 Log.d("MessageList", "ACCOUNT:" + names[0] + "MAILBOX" + names[1]); 1033 if (names[0] != null) { 1034 mRightTitle.setText(names[0]); 1035 } 1036 if (names[1] != null) { 1037 mLeftTitle.setText(names[1]); 1038 } 1039 } 1040 } 1041 1042 /** 1043 * Handler for UI-thread operations (when called from callbacks or any other threads) 1044 */ 1045 class MessageListHandler extends Handler { 1046 private static final int MSG_PROGRESS = 1; 1047 private static final int MSG_LOOKUP_MAILBOX_TYPE = 2; 1048 1049 @Override 1050 public void handleMessage(android.os.Message msg) { 1051 switch (msg.what) { 1052 case MSG_PROGRESS: 1053 boolean visible = (msg.arg1 != 0); 1054 if (visible) { 1055 mProgressIcon.setVisibility(View.VISIBLE); 1056 } else { 1057 mProgressIcon.setVisibility(View.GONE); 1058 } 1059 if (mListFooterProgress != null) { 1060 mListFooterProgress.setVisibility(visible ? View.VISIBLE : View.GONE); 1061 } 1062 setListFooterText(visible); 1063 break; 1064 case MSG_LOOKUP_MAILBOX_TYPE: 1065 // kill running async task, if any 1066 if (mFindMailboxTask != null && 1067 mFindMailboxTask.getStatus() != FindMailboxTask.Status.FINISHED) { 1068 mFindMailboxTask.cancel(true); 1069 mFindMailboxTask = null; 1070 } 1071 // start new one. do not recurse back to controller. 1072 long accountId = ((Long)msg.obj).longValue(); 1073 int mailboxType = msg.arg1; 1074 mFindMailboxTask = new FindMailboxTask(accountId, mailboxType, false); 1075 mFindMailboxTask.execute(); 1076 break; 1077 default: 1078 super.handleMessage(msg); 1079 } 1080 } 1081 1082 /** 1083 * Call from any thread to start/stop progress indicator(s) 1084 * @param progress true to start, false to stop 1085 */ 1086 public void progress(boolean progress) { 1087 android.os.Message msg = android.os.Message.obtain(); 1088 msg.what = MSG_PROGRESS; 1089 msg.arg1 = progress ? 1 : 0; 1090 sendMessage(msg); 1091 } 1092 1093 /** 1094 * Called from any thread to look for a mailbox of a specific type. This is designed 1095 * to be called from the Controller's MailboxList callback; It instructs the async task 1096 * not to recurse, in case the mailbox is not found after this. 1097 * 1098 * See FindMailboxTask for more notes on this handler. 1099 */ 1100 public void lookupMailboxType(long accountId, int mailboxType) { 1101 android.os.Message msg = android.os.Message.obtain(); 1102 msg.what = MSG_LOOKUP_MAILBOX_TYPE; 1103 msg.arg1 = mailboxType; 1104 msg.obj = Long.valueOf(accountId); 1105 sendMessage(msg); 1106 } 1107 } 1108 1109 /** 1110 * Callback for async Controller results. 1111 */ 1112 private class ControllerResults implements Controller.Result { 1113 1114 // These are preset for use by updateMailboxListCallback 1115 int mWaitForMailboxType = -1; 1116 1117 // TODO report errors into UI 1118 // TODO check accountKey and only react to relevant notifications 1119 public void updateMailboxListCallback(MessagingException result, long accountKey, 1120 int progress) { 1121 updateProgress(result, progress); 1122 if (progress == 100) { 1123 mHandler.lookupMailboxType(accountKey, mWaitForMailboxType); 1124 } 1125 } 1126 1127 // TODO report errors into UI 1128 // TODO check accountKey and only react to relevant notifications 1129 public void updateMailboxCallback(MessagingException result, long accountKey, 1130 long mailboxKey, int progress, int numNewMessages) { 1131 updateProgress(result, progress); 1132 } 1133 1134 public void loadMessageForViewCallback(MessagingException result, long messageId, 1135 int progress) { 1136 } 1137 1138 public void loadAttachmentCallback(MessagingException result, long messageId, 1139 long attachmentId, int progress) { 1140 } 1141 1142 public void serviceCheckMailCallback(MessagingException result, long accountId, 1143 long mailboxId, int progress, long tag) { 1144 } 1145 1146 // TODO report errors into UI 1147 public void sendMailCallback(MessagingException result, long accountId, long messageId, 1148 int progress) { 1149 if (mListFooterMode == LIST_FOOTER_MODE_SEND) { 1150 updateProgress(result, progress); 1151 } 1152 } 1153 1154 private void updateProgress(MessagingException result, int progress) { 1155 if (result != null || progress == 100) { 1156 mHandler.progress(false); 1157 } else if (progress == 0) { 1158 mHandler.progress(true); 1159 } 1160 } 1161 } 1162 1163 /** 1164 * This class implements the adapter for displaying messages based on cursors. 1165 */ 1166 /* package */ class MessageListAdapter extends CursorAdapter { 1167 1168 public static final int COLUMN_ID = 0; 1169 public static final int COLUMN_MAILBOX_KEY = 1; 1170 public static final int COLUMN_ACCOUNT_KEY = 2; 1171 public static final int COLUMN_DISPLAY_NAME = 3; 1172 public static final int COLUMN_SUBJECT = 4; 1173 public static final int COLUMN_DATE = 5; 1174 public static final int COLUMN_READ = 6; 1175 public static final int COLUMN_FAVORITE = 7; 1176 public static final int COLUMN_ATTACHMENTS = 8; 1177 1178 public final String[] PROJECTION = new String[] { 1179 EmailContent.RECORD_ID, MessageColumns.MAILBOX_KEY, MessageColumns.ACCOUNT_KEY, 1180 MessageColumns.DISPLAY_NAME, MessageColumns.SUBJECT, MessageColumns.TIMESTAMP, 1181 MessageColumns.FLAG_READ, MessageColumns.FLAG_FAVORITE, MessageColumns.FLAG_ATTACHMENT, 1182 }; 1183 1184 Context mContext; 1185 private LayoutInflater mInflater; 1186 private Drawable mAttachmentIcon; 1187 private Drawable mFavoriteIconOn; 1188 private Drawable mFavoriteIconOff; 1189 private Drawable mSelectedIconOn; 1190 private Drawable mSelectedIconOff; 1191 1192 private java.text.DateFormat mDateFormat; 1193 private java.text.DateFormat mDayFormat; 1194 private java.text.DateFormat mTimeFormat; 1195 1196 private HashSet<Long> mChecked = new HashSet<Long>(); 1197 1198 public MessageListAdapter(Context context) { 1199 super(context, null); 1200 mContext = context; 1201 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 1202 1203 Resources resources = context.getResources(); 1204 mAttachmentIcon = resources.getDrawable(R.drawable.ic_mms_attachment_small); 1205 mFavoriteIconOn = resources.getDrawable(android.R.drawable.star_on); 1206 mFavoriteIconOff = resources.getDrawable(android.R.drawable.star_off); 1207 mSelectedIconOn = resources.getDrawable(R.drawable.btn_check_buttonless_on); 1208 mSelectedIconOff = resources.getDrawable(R.drawable.btn_check_buttonless_off); 1209 1210 mDateFormat = android.text.format.DateFormat.getDateFormat(context); // short date 1211 mDayFormat = android.text.format.DateFormat.getDateFormat(context); // TODO: day 1212 mTimeFormat = android.text.format.DateFormat.getTimeFormat(context); // 12/24 time 1213 } 1214 1215 public Set<Long> getSelectedSet() { 1216 return mChecked; 1217 } 1218 1219 @Override 1220 public void bindView(View view, Context context, Cursor cursor) { 1221 // Reset the view (in case it was recycled) and prepare for binding 1222 MessageListItem itemView = (MessageListItem) view; 1223 itemView.bindViewInit(this, true); 1224 1225 // Load the public fields in the view (for later use) 1226 itemView.mMessageId = cursor.getLong(COLUMN_ID); 1227 itemView.mMailboxId = cursor.getLong(COLUMN_MAILBOX_KEY); 1228 itemView.mAccountId = cursor.getLong(COLUMN_ACCOUNT_KEY); 1229 itemView.mRead = cursor.getInt(COLUMN_READ) != 0; 1230 itemView.mFavorite = cursor.getInt(COLUMN_FAVORITE) != 0; 1231 itemView.mSelected = mChecked.contains(Long.valueOf(itemView.mMessageId)); 1232 1233 // Load the UI 1234 View chipView = view.findViewById(R.id.chip); 1235 int chipResId = mColorChipResIds[(int)itemView.mAccountId % mColorChipResIds.length]; 1236 chipView.setBackgroundResource(chipResId); 1237 // TODO always display chip. Use other indications (e.g. boldface) for read/unread 1238 chipView.getBackground().setAlpha(itemView.mRead ? 100 : 255); 1239 1240 TextView fromView = (TextView) view.findViewById(R.id.from); 1241 String text = cursor.getString(COLUMN_DISPLAY_NAME); 1242 fromView.setText(text); 1243 1244 boolean hasAttachments = cursor.getInt(COLUMN_ATTACHMENTS) != 0; 1245 fromView.setCompoundDrawablesWithIntrinsicBounds(null, null, 1246 hasAttachments ? mAttachmentIcon : null, null); 1247 1248 TextView subjectView = (TextView) view.findViewById(R.id.subject); 1249 text = cursor.getString(COLUMN_SUBJECT); 1250 subjectView.setText(text); 1251 1252 // TODO ui spec suggests "time", "day", "date" - implement "day" 1253 TextView dateView = (TextView) view.findViewById(R.id.date); 1254 long timestamp = cursor.getLong(COLUMN_DATE); 1255 Date date = new Date(timestamp); 1256 if (Utility.isDateToday(date)) { 1257 text = mTimeFormat.format(date); 1258 } else { 1259 text = mDateFormat.format(date); 1260 } 1261 dateView.setText(text); 1262 1263 ImageView selectedView = (ImageView) view.findViewById(R.id.selected); 1264 selectedView.setImageDrawable(itemView.mSelected ? mSelectedIconOn : mSelectedIconOff); 1265 1266 ImageView favoriteView = (ImageView) view.findViewById(R.id.favorite); 1267 favoriteView.setImageDrawable(itemView.mFavorite ? mFavoriteIconOn : mFavoriteIconOff); 1268 } 1269 1270 @Override 1271 public View newView(Context context, Cursor cursor, ViewGroup parent) { 1272 return mInflater.inflate(R.layout.message_list_item, parent, false); 1273 } 1274 1275 /** 1276 * This is used as a callback from the list items, to set the selected state 1277 * 1278 * @param itemView the item being changed 1279 * @param newSelected the new value of the selected flag (checkbox state) 1280 */ 1281 public void updateSelected(MessageListItem itemView, boolean newSelected) { 1282 ImageView selectedView = (ImageView) itemView.findViewById(R.id.selected); 1283 selectedView.setImageDrawable(newSelected ? mSelectedIconOn : mSelectedIconOff); 1284 1285 // Set checkbox state in list, and show/hide panel if necessary 1286 Long id = Long.valueOf(itemView.mMessageId); 1287 if (newSelected) { 1288 mChecked.add(id); 1289 } else { 1290 mChecked.remove(id); 1291 } 1292 1293 MessageList.this.showMultiPanel(mChecked.size() > 0); 1294 } 1295 1296 /** 1297 * This is used as a callback from the list items, to set the favorite state 1298 * 1299 * @param itemView the item being changed 1300 * @param newFavorite the new value of the favorite flag (star state) 1301 */ 1302 public void updateFavorite(MessageListItem itemView, boolean newFavorite) { 1303 ImageView favoriteView = (ImageView) itemView.findViewById(R.id.favorite); 1304 favoriteView.setImageDrawable(newFavorite ? mFavoriteIconOn : mFavoriteIconOff); 1305 onSetMessageFavorite(itemView.mMessageId, newFavorite); 1306 } 1307 } 1308} 1309