MessageList.java revision 8ade2fe010797be45d5c0f9023e5d76bcc3b50a8
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.ControllerResultUiThreadWrapper; 21import com.android.email.Email; 22import com.android.email.R; 23import com.android.email.Utility; 24import com.android.email.activity.setup.AccountSecurity; 25import com.android.email.activity.setup.AccountSettings; 26import com.android.email.mail.AuthenticationFailedException; 27import com.android.email.mail.CertificateValidationException; 28import com.android.email.mail.MessagingException; 29import com.android.email.provider.EmailContent; 30import com.android.email.provider.EmailContent.Account; 31import com.android.email.provider.EmailContent.AccountColumns; 32import com.android.email.provider.EmailContent.Mailbox; 33import com.android.email.provider.EmailContent.MailboxColumns; 34import com.android.email.service.MailService; 35 36import android.app.Activity; 37import android.app.NotificationManager; 38import android.content.ContentResolver; 39import android.content.ContentUris; 40import android.content.Context; 41import android.content.Intent; 42import android.database.Cursor; 43import android.net.Uri; 44import android.os.AsyncTask; 45import android.os.Bundle; 46import android.os.Handler; 47import android.view.Menu; 48import android.view.MenuItem; 49import android.view.View; 50import android.view.View.OnClickListener; 51import android.view.animation.Animation; 52import android.view.animation.AnimationUtils; 53import android.view.animation.Animation.AnimationListener; 54import android.widget.Button; 55import android.widget.ProgressBar; 56import android.widget.TextView; 57 58public class MessageList extends Activity implements OnClickListener, 59 AnimationListener, MessageListFragment.Callback { 60 // Intent extras (internal to this activity) 61 private static final String EXTRA_ACCOUNT_ID = "com.android.email.activity._ACCOUNT_ID"; 62 private static final String EXTRA_MAILBOX_TYPE = "com.android.email.activity.MAILBOX_TYPE"; 63 private static final String EXTRA_MAILBOX_ID = "com.android.email.activity.MAILBOX_ID"; 64 65 private static final int REQUEST_SECURITY = 0; 66 67 // UI support 68 private MessageListFragment mListFragment; 69 private View mMultiSelectPanel; 70 private Button mReadUnreadButton; 71 private Button mFavoriteButton; 72 private Button mDeleteButton; 73 private TextView mErrorBanner; 74 75 private final Controller mController = Controller.getInstance(getApplication()); 76 private ControllerResultUiThreadWrapper<ControllerResults> mControllerCallback; 77 78 private TextView mLeftTitle; 79 private ProgressBar mProgressIcon; 80 81 // DB access 82 private ContentResolver mResolver; 83 private FindMailboxTask mFindMailboxTask; 84 private SetTitleTask mSetTitleTask; 85 86 private static final int MAILBOX_NAME_COLUMN_ID = 0; 87 private static final int MAILBOX_NAME_COLUMN_ACCOUNT_KEY = 1; 88 private static final int MAILBOX_NAME_COLUMN_TYPE = 2; 89 private static final String[] MAILBOX_NAME_PROJECTION = new String[] { 90 MailboxColumns.DISPLAY_NAME, MailboxColumns.ACCOUNT_KEY, 91 MailboxColumns.TYPE}; 92 93 private static final int ACCOUNT_DISPLAY_NAME_COLUMN_ID = 0; 94 private static final String[] ACCOUNT_NAME_PROJECTION = new String[] { 95 AccountColumns.DISPLAY_NAME }; 96 97 private static final int ACCOUNT_INFO_COLUMN_FLAGS = 0; 98 private static final String[] ACCOUNT_INFO_PROJECTION = new String[] { 99 AccountColumns.FLAGS }; 100 101 private static final String ID_SELECTION = EmailContent.RECORD_ID + "=?"; 102 103 /* package */ MessageListFragment getListFragmentForTest() { 104 return mListFragment; 105 } 106 107 /** 108 * Open a specific mailbox. 109 * 110 * TODO This should just shortcut to a more generic version that can accept a list of 111 * accounts/mailboxes (e.g. merged inboxes). 112 * 113 * @param context 114 * @param id mailbox key 115 */ 116 public static void actionHandleMailbox(Context context, long id) { 117 context.startActivity(createIntent(context, -1, id, -1)); 118 } 119 120 /** 121 * Open a specific mailbox by account & type 122 * 123 * @param context The caller's context (for generating an intent) 124 * @param accountId The account to open 125 * @param mailboxType the type of mailbox to open (e.g. @see EmailContent.Mailbox.TYPE_INBOX) 126 */ 127 public static void actionHandleAccount(Context context, long accountId, int mailboxType) { 128 context.startActivity(createIntent(context, accountId, -1, mailboxType)); 129 } 130 131 /** 132 * Open the inbox of the account with a UUID. It's used to handle old style 133 * (Android <= 1.6) desktop shortcut intents. 134 */ 135 public static void actionOpenAccountInboxUuid(Context context, String accountUuid) { 136 Intent i = createIntent(context, -1, -1, Mailbox.TYPE_INBOX); 137 i.setData(Account.getShortcutSafeUriFromUuid(accountUuid)); 138 context.startActivity(i); 139 } 140 141 /** 142 * Return an intent to open a specific mailbox by account & type. 143 * 144 * @param context The caller's context (for generating an intent) 145 * @param accountId The account to open, or -1 146 * @param mailboxId the ID of the mailbox to open, or -1 147 * @param mailboxType the type of mailbox to open (e.g. @see Mailbox.TYPE_INBOX) or -1 148 */ 149 public static Intent createIntent(Context context, long accountId, long mailboxId, 150 int mailboxType) { 151 Intent intent = new Intent(context, MessageList.class); 152 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 153 if (accountId != -1) intent.putExtra(EXTRA_ACCOUNT_ID, accountId); 154 if (mailboxId != -1) intent.putExtra(EXTRA_MAILBOX_ID, mailboxId); 155 if (mailboxType != -1) intent.putExtra(EXTRA_MAILBOX_TYPE, mailboxType); 156 return intent; 157 } 158 159 /** 160 * Create and return an intent for a desktop shortcut for an account. 161 * 162 * @param context Calling context for building the intent 163 * @param account The account of interest 164 * @param mailboxType The folder name to open (typically Mailbox.TYPE_INBOX) 165 * @return an Intent which can be used to view that account 166 */ 167 public static Intent createAccountIntentForShortcut(Context context, Account account, 168 int mailboxType) { 169 Intent i = createIntent(context, -1, -1, mailboxType); 170 i.setData(account.getShortcutSafeUri()); 171 return i; 172 } 173 174 @Override 175 public void onCreate(Bundle icicle) { 176 super.onCreate(icicle); 177 setContentView(R.layout.message_list); 178 179 mControllerCallback = new ControllerResultUiThreadWrapper<ControllerResults>( 180 new Handler(), new ControllerResults()); 181 mListFragment = (MessageListFragment) findFragmentById(R.id.list); 182 mMultiSelectPanel = findViewById(R.id.footer_organize); 183 mReadUnreadButton = (Button) findViewById(R.id.btn_read_unread); 184 mFavoriteButton = (Button) findViewById(R.id.btn_multi_favorite); 185 mDeleteButton = (Button) findViewById(R.id.btn_multi_delete); 186 mLeftTitle = (TextView) findViewById(R.id.title_left_text); 187 mProgressIcon = (ProgressBar) findViewById(R.id.title_progress_icon); 188 mErrorBanner = (TextView) findViewById(R.id.connection_error_text); 189 190 mReadUnreadButton.setOnClickListener(this); 191 mFavoriteButton.setOnClickListener(this); 192 mDeleteButton.setOnClickListener(this); 193 ((Button) findViewById(R.id.account_title_button)).setOnClickListener(this); 194 195 mListFragment.setCallback(this); 196 197 mResolver = getContentResolver(); 198 199 // Show the appropriate account/mailbox specified by an {@link Intent}. 200 selectAccountAndMailbox(getIntent()); 201 } 202 203 /** 204 * Show the appropriate account/mailbox specified by an {@link Intent}. 205 */ 206 private void selectAccountAndMailbox(Intent intent) { 207 long mailboxId = intent.getLongExtra(EXTRA_MAILBOX_ID, -1); 208 if (mailboxId != -1) { 209 // Specific mailbox ID was provided - go directly to it 210 mSetTitleTask = new SetTitleTask(mailboxId); 211 mSetTitleTask.execute(); 212 mListFragment.openMailbox(-1, mailboxId); 213 } else { 214 int mailboxType = intent.getIntExtra(EXTRA_MAILBOX_TYPE, Mailbox.TYPE_INBOX); 215 Uri uri = intent.getData(); 216 // TODO Possible ANR. getAccountIdFromShortcutSafeUri accesses DB. 217 long accountId = (uri == null) ? -1 218 : Account.getAccountIdFromShortcutSafeUri(this, uri); 219 220 // TODO Both branches have almost identical code. Unify them. 221 // (And why not "okay to recurse" in the first case?) 222 if (accountId != -1) { 223 // A content URI was provided - try to look up the account 224 mFindMailboxTask = new FindMailboxTask(accountId, mailboxType, false); 225 mFindMailboxTask.execute(); 226 } else { 227 // Go by account id + type 228 accountId = intent.getLongExtra(EXTRA_ACCOUNT_ID, -1); 229 mFindMailboxTask = new FindMailboxTask(accountId, mailboxType, true); 230 mFindMailboxTask.execute(); 231 } 232 } 233 // TODO set title to "account > mailbox (#unread)" 234 } 235 236 @Override 237 public void onPause() { 238 super.onPause(); 239 mController.removeResultCallback(mControllerCallback); 240 } 241 242 @Override 243 public void onResume() { 244 super.onResume(); 245 mController.addResultCallback(mControllerCallback); 246 247 // clear notifications here 248 NotificationManager notificationManager = (NotificationManager) 249 getSystemService(Context.NOTIFICATION_SERVICE); 250 notificationManager.cancel(MailService.NOTIFICATION_ID_NEW_MESSAGES); 251 252 // Exit immediately if the accounts list has changed (e.g. externally deleted) 253 if (Email.getNotifyUiAccountsChanged()) { 254 Welcome.actionStart(this); 255 finish(); 256 return; 257 } 258 } 259 260 @Override 261 protected void onDestroy() { 262 super.onDestroy(); 263 264 Utility.cancelTaskInterrupt(mFindMailboxTask); 265 mFindMailboxTask = null; 266 Utility.cancelTaskInterrupt(mSetTitleTask); 267 mSetTitleTask = null; 268 } 269 270 /** 271 * Called when the list fragment can't find mailbox/account. 272 */ 273 public void onMailboxNotFound() { 274 finish(); 275 } 276 277 public void onMessageOpen(final long messageId, final long mailboxId) { 278 final Context context = this; // Make the code shorter. 279 Utility.runAsync(new Runnable() { 280 public void run() { 281 EmailContent.Mailbox mailbox = EmailContent.Mailbox.restoreMailboxWithId(context, 282 mailboxId); 283 if (mailbox == null) { 284 return; 285 } 286 287 if (mailbox.mType == EmailContent.Mailbox.TYPE_DRAFTS) { 288 MessageCompose.actionEditDraft(context, messageId); 289 } else { 290 final boolean disableReply = (mailbox.mType == EmailContent.Mailbox.TYPE_TRASH); 291 // WARNING: here we pass getMailboxId(), which can be the negative id of 292 // a compound mailbox, instead of the mailboxId of the particular message that 293 // is opened. This is to support the next/prev buttons on the message view 294 // properly even for combined mailboxes. 295 MessageView.actionView(context, messageId, mListFragment.getMailboxId(), 296 disableReply); 297 } 298 } 299 }); 300 } 301 302 public void onMessageReply(long messageId) { 303 MessageCompose.actionReply(this, messageId, false); 304 } 305 306 public void onMessageReplyAll(long messageId) { 307 MessageCompose.actionReply(this, messageId, true); 308 } 309 310 public void onMessageForward(long messageId) { 311 MessageCompose.actionForward(this, messageId); 312 } 313 314 public void onClick(View v) { 315 switch (v.getId()) { 316 case R.id.btn_read_unread: 317 mListFragment.onMultiToggleRead(); 318 break; 319 case R.id.btn_multi_favorite: 320 mListFragment.onMultiToggleFavorite(); 321 break; 322 case R.id.btn_multi_delete: 323 mListFragment.onMultiDelete(); 324 break; 325 case R.id.account_title_button: 326 onAccounts(); 327 break; 328 } 329 } 330 331 public void onAnimationEnd(Animation animation) { 332 mListFragment.updateListPosition(); 333 } 334 335 public void onAnimationRepeat(Animation animation) { 336 } 337 338 public void onAnimationStart(Animation animation) { 339 } 340 341 @Override 342 public boolean onPrepareOptionsMenu(Menu menu) { 343 // Re-create menu every time. (We may not know the mailbox id yet) 344 menu.clear(); 345 if (mListFragment.isMagicMailbox()) { 346 getMenuInflater().inflate(R.menu.message_list_option_smart_folder, menu); 347 } else { 348 getMenuInflater().inflate(R.menu.message_list_option, menu); 349 } 350 boolean showDeselect = mListFragment.getSelectedCount() > 0; 351 menu.setGroupVisible(R.id.deselect_all_group, showDeselect); 352 return true; 353 } 354 355 @Override 356 public boolean onOptionsItemSelected(MenuItem item) { 357 switch (item.getItemId()) { 358 case R.id.refresh: 359 mListFragment.onRefresh(); 360 return true; 361 case R.id.folders: 362 onFolders(); 363 return true; 364 case R.id.accounts: 365 onAccounts(); 366 return true; 367 case R.id.compose: 368 onCompose(); 369 return true; 370 case R.id.account_settings: 371 onEditAccount(); 372 return true; 373 case R.id.deselect_all: 374 mListFragment.onDeselectAll(); 375 return true; 376 default: 377 return super.onOptionsItemSelected(item); 378 } 379 } 380 381 private void onFolders() { 382 if (!mListFragment.isMagicMailbox()) { // Magic boxes don't have "folders" option. 383 // TODO smaller projection 384 Mailbox mailbox = Mailbox.restoreMailboxWithId(this, mListFragment.getMailboxId()); 385 if (mailbox != null) { 386 MailboxList.actionHandleAccount(this, mailbox.mAccountKey); 387 finish(); 388 } 389 } 390 } 391 392 private void onAccounts() { 393 AccountFolderList.actionShowAccounts(this); 394 finish(); 395 } 396 397 private void onCompose() { 398 MessageCompose.actionCompose(this, mListFragment.getAccountId()); 399 } 400 401 private void onEditAccount() { 402 if (!mListFragment.isMagicMailbox()) { // Magic boxes don't have "accout settings" option. 403 AccountSettings.actionSettings(this, mListFragment.getAccountId()); 404 } 405 } 406 407 /** 408 * Show multi-selection panel, if one or more messages are selected. Button labels will be 409 * updated too. 410 */ 411 public void onSelectionChanged() { 412 showMultiPanel(mListFragment.getSelectedCount() > 0); 413 } 414 415 private void updateFooterButtonNames () { 416 // Show "unread_action" when one or more read messages are selected. 417 if (mListFragment.doesSelectionContainReadMessage()) { 418 mReadUnreadButton.setText(R.string.unread_action); 419 } else { 420 mReadUnreadButton.setText(R.string.read_action); 421 } 422 // Show "set_star_action" when one or more un-starred messages are selected. 423 if (mListFragment.doesSelectionContainNonStarredMessage()) { 424 mFavoriteButton.setText(R.string.set_star_action); 425 } else { 426 mFavoriteButton.setText(R.string.remove_star_action); 427 } 428 } 429 430 /** 431 * Show or hide the panel of multi-select options 432 */ 433 private void showMultiPanel(boolean show) { 434 if (show && mMultiSelectPanel.getVisibility() != View.VISIBLE) { 435 mMultiSelectPanel.setVisibility(View.VISIBLE); 436 Animation animation = AnimationUtils.loadAnimation(this, R.anim.footer_appear); 437 animation.setAnimationListener(this); 438 mMultiSelectPanel.startAnimation(animation); 439 } else if (!show && mMultiSelectPanel.getVisibility() != View.GONE) { 440 mMultiSelectPanel.setVisibility(View.GONE); 441 mMultiSelectPanel.startAnimation( 442 AnimationUtils.loadAnimation(this, R.anim.footer_disappear)); 443 } 444 if (show) { 445 updateFooterButtonNames(); 446 } 447 } 448 449 /** 450 * Async task for finding a single mailbox by type (possibly even going to the network). 451 * 452 * This is much too complex, as implemented. It uses this AsyncTask to check for a mailbox, 453 * then (if not found) a Controller call to refresh mailboxes from the server, and a handler 454 * to relaunch this task (a 2nd time) to read the results of the network refresh. The core 455 * problem is that we have two different non-UI-thread jobs (reading DB and reading network) 456 * and two different paradigms for dealing with them. Some unification would be needed here 457 * to make this cleaner. 458 * 459 * TODO: If this problem spreads to other operations, find a cleaner way to handle it. 460 */ 461 private class FindMailboxTask extends AsyncTask<Void, Void, Long> { 462 463 private final long mAccountId; 464 private final int mMailboxType; 465 private final boolean mOkToRecurse; 466 467 private static final int ACTION_DEFAULT = 0; 468 private static final int SHOW_WELCOME_ACTIVITY = 1; 469 private static final int SHOW_SECURITY_ACTIVITY = 2; 470 private static final int START_NETWORK_LOOK_UP = 3; 471 private int mAction = ACTION_DEFAULT; 472 473 /** 474 * Special constructor to cache some local info 475 */ 476 public FindMailboxTask(long accountId, int mailboxType, boolean okToRecurse) { 477 mAccountId = accountId; 478 mMailboxType = mailboxType; 479 mOkToRecurse = okToRecurse; 480 } 481 482 @Override 483 protected Long doInBackground(Void... params) { 484 // Quick check that account is not in security hold 485 if (mAccountId != -1 && isSecurityHold(mAccountId)) { 486 mAction = SHOW_SECURITY_ACTIVITY; 487 return Mailbox.NO_MAILBOX; 488 } 489 // See if we can find the requested mailbox in the DB. 490 long mailboxId = Mailbox.findMailboxOfType(MessageList.this, mAccountId, mMailboxType); 491 if (mailboxId == Mailbox.NO_MAILBOX) { 492 // Mailbox not found. Does the account really exists? 493 final boolean accountExists = Account.isValidId(MessageList.this, mAccountId); 494 if (accountExists && mOkToRecurse) { 495 // launch network lookup 496 mAction = START_NETWORK_LOOK_UP; 497 } else { 498 // We don't want to do the network lookup, or the account doesn't exist in the 499 // first place. 500 mAction = SHOW_WELCOME_ACTIVITY; 501 } 502 } 503 return mailboxId; 504 } 505 506 @Override 507 protected void onPostExecute(Long mailboxId) { 508 switch (mAction) { 509 case SHOW_SECURITY_ACTIVITY: 510 // launch the security setup activity 511 Intent i = AccountSecurity.actionUpdateSecurityIntent( 512 MessageList.this, mAccountId); 513 MessageList.this.startActivityForResult(i, REQUEST_SECURITY); 514 return; 515 case SHOW_WELCOME_ACTIVITY: 516 // Let the Welcome activity show the default screen. 517 Welcome.actionStart(MessageList.this); 518 finish(); 519 return; 520 case START_NETWORK_LOOK_UP: 521 mControllerCallback.getWrappee().presetMailboxListCallback( 522 mMailboxType, mAccountId); 523 524 // TODO updateMailboxList accessed DB, so we shouldn't call on the UI thread, 525 // but we should fix the Controller side. (Other Controller methods too access 526 // DB but are called on the UI thread.) 527 mController.updateMailboxList(mAccountId); 528 return; 529 default: 530 // At this point, mailboxId != NO_MAILBOX 531 mSetTitleTask = new SetTitleTask(mailboxId); 532 mSetTitleTask.execute(); 533 mListFragment.openMailbox(mAccountId, mailboxId); 534 return; 535 } 536 } 537 } 538 539 /** 540 * Check a single account for security hold status. Do not call from UI thread. 541 */ 542 private boolean isSecurityHold(long accountId) { 543 Cursor c = MessageList.this.getContentResolver().query( 544 ContentUris.withAppendedId(Account.CONTENT_URI, accountId), 545 ACCOUNT_INFO_PROJECTION, null, null, null); 546 try { 547 if (c.moveToFirst()) { 548 int flags = c.getInt(ACCOUNT_INFO_COLUMN_FLAGS); 549 if ((flags & Account.FLAGS_SECURITY_HOLD) != 0) { 550 return true; 551 } 552 } 553 } finally { 554 c.close(); 555 } 556 return false; 557 } 558 559 /** 560 * Handle the eventual result from the security update activity 561 * 562 * Note, this is extremely coarse, and it simply returns the user to the Accounts list. 563 * Anything more requires refactoring of this Activity. 564 */ 565 @Override 566 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 567 switch (requestCode) { 568 case REQUEST_SECURITY: 569 onAccounts(); 570 } 571 super.onActivityResult(requestCode, resultCode, data); 572 } 573 574 private class SetTitleTask extends AsyncTask<Void, Void, Object[]> { 575 576 private long mMailboxKey; 577 578 public SetTitleTask(long mailboxKey) { 579 mMailboxKey = mailboxKey; 580 } 581 582 @Override 583 protected Object[] doInBackground(Void... params) { 584 // Check special Mailboxes 585 int resIdSpecialMailbox = 0; 586 if (mMailboxKey == Mailbox.QUERY_ALL_INBOXES) { 587 resIdSpecialMailbox = R.string.account_folder_list_summary_inbox; 588 } else if (mMailboxKey == Mailbox.QUERY_ALL_FAVORITES) { 589 resIdSpecialMailbox = R.string.account_folder_list_summary_starred; 590 } else if (mMailboxKey == Mailbox.QUERY_ALL_DRAFTS) { 591 resIdSpecialMailbox = R.string.account_folder_list_summary_drafts; 592 } else if (mMailboxKey == Mailbox.QUERY_ALL_OUTBOX) { 593 resIdSpecialMailbox = R.string.account_folder_list_summary_outbox; 594 } 595 if (resIdSpecialMailbox != 0) { 596 return new Object[] {null, getString(resIdSpecialMailbox), 0}; 597 } 598 599 String accountName = null; 600 String mailboxName = null; 601 String accountKey = null; 602 Cursor c = MessageList.this.mResolver.query(Mailbox.CONTENT_URI, 603 MAILBOX_NAME_PROJECTION, ID_SELECTION, 604 new String[] { Long.toString(mMailboxKey) }, null); 605 try { 606 if (c.moveToFirst()) { 607 mailboxName = Utility.FolderProperties.getInstance(MessageList.this) 608 .getDisplayName(c.getInt(MAILBOX_NAME_COLUMN_TYPE)); 609 if (mailboxName == null) { 610 mailboxName = c.getString(MAILBOX_NAME_COLUMN_ID); 611 } 612 accountKey = c.getString(MAILBOX_NAME_COLUMN_ACCOUNT_KEY); 613 } 614 } finally { 615 c.close(); 616 } 617 if (accountKey != null) { 618 c = MessageList.this.mResolver.query(Account.CONTENT_URI, 619 ACCOUNT_NAME_PROJECTION, ID_SELECTION, new String[] { accountKey }, 620 null); 621 try { 622 if (c.moveToFirst()) { 623 accountName = c.getString(ACCOUNT_DISPLAY_NAME_COLUMN_ID); 624 } 625 } finally { 626 c.close(); 627 } 628 } 629 int nAccounts = EmailContent.count(MessageList.this, Account.CONTENT_URI, null, null); 630 return new Object[] {accountName, mailboxName, nAccounts}; 631 } 632 633 @Override 634 protected void onPostExecute(Object[] result) { 635 if (result == null) { 636 return; 637 } 638 639 final int nAccounts = (Integer) result[2]; 640 if (result[0] != null) { 641 setTitleAccountName((String) result[0], nAccounts > 1); 642 } 643 644 if (result[1] != null) { 645 mLeftTitle.setText((String) result[1]); 646 } 647 } 648 } 649 650 private void setTitleAccountName(String accountName, boolean showAccountsButton) { 651 TextView accountsButton = (TextView) findViewById(R.id.account_title_button); 652 TextView textPlain = (TextView) findViewById(R.id.title_right_text); 653 if (showAccountsButton) { 654 accountsButton.setVisibility(View.VISIBLE); 655 textPlain.setVisibility(View.GONE); 656 accountsButton.setText(accountName); 657 } else { 658 accountsButton.setVisibility(View.GONE); 659 textPlain.setVisibility(View.VISIBLE); 660 textPlain.setText(accountName); 661 } 662 } 663 664 private void showProgressIcon(boolean show) { 665 int visibility = show ? View.VISIBLE : View.GONE; 666 mProgressIcon.setVisibility(visibility); 667 mListFragment.showProgressIcon(show); 668 } 669 670 private void lookupMailboxType(long accountId, int mailboxType) { 671 // kill running async task, if any 672 Utility.cancelTaskInterrupt(mFindMailboxTask); 673 // start new one. do not recurse back to controller. 674 mFindMailboxTask = new FindMailboxTask(accountId, mailboxType, false); 675 mFindMailboxTask.execute(); 676 } 677 678 private void showErrorBanner(String message) { 679 boolean isVisible = mErrorBanner.getVisibility() == View.VISIBLE; 680 if (message != null) { 681 mErrorBanner.setText(message); 682 if (!isVisible) { 683 mErrorBanner.setVisibility(View.VISIBLE); 684 mErrorBanner.startAnimation( 685 AnimationUtils.loadAnimation( 686 MessageList.this, R.anim.header_appear)); 687 } 688 } else { 689 if (isVisible) { 690 mErrorBanner.setVisibility(View.GONE); 691 mErrorBanner.startAnimation( 692 AnimationUtils.loadAnimation( 693 MessageList.this, R.anim.header_disappear)); 694 } 695 } 696 } 697 698 /** 699 * Controller results listener. We wrap it with {@link ControllerResultUiThreadWrapper}, 700 * so all methods are called on the UI thread. 701 */ 702 private class ControllerResults extends Controller.Result { 703 704 // This is used to alter the connection banner operation for sending messages 705 MessagingException mSendMessageException; 706 707 // These values are set by FindMailboxTask. 708 private int mWaitForMailboxType = -1; 709 private long mWaitForMailboxAccount = -1; 710 711 public void presetMailboxListCallback(int mailboxType, long accountId) { 712 mWaitForMailboxType = mailboxType; 713 mWaitForMailboxAccount = accountId; 714 } 715 716 @Override 717 public void updateMailboxListCallback(MessagingException result, 718 long accountKey, int progress) { 719 // updateMailboxList is never the end goal in MessageList, so we don't show 720 // these errors. There are a couple of corner cases that we miss reporting, but 721 // this is better than reporting a number of non-problem intermediate states. 722 // updateBanner(result, progress, mMailboxId); 723 724 updateProgress(result, progress); 725 if (progress == 100 && accountKey == mWaitForMailboxAccount) { 726 mWaitForMailboxAccount = -1; 727 lookupMailboxType(accountKey, mWaitForMailboxType); 728 } 729 } 730 731 // TODO check accountKey and only react to relevant notifications 732 @Override 733 public void updateMailboxCallback(MessagingException result, long accountKey, 734 long mailboxKey, int progress, int numNewMessages) { 735 updateBanner(result, progress, mailboxKey); 736 if (result != null || progress == 100) { 737 Email.updateMailboxRefreshTime(mailboxKey); 738 } 739 updateProgress(result, progress); 740 } 741 742 /** 743 * We alter the updateBanner hysteresis here to capture any failures and handle 744 * them just once at the end. This callback is overly overloaded: 745 * result == null, messageId == -1, progress == 0: start batch send 746 * result == null, messageId == xx, progress == 0: start sending one message 747 * result == xxxx, messageId == xx, progress == 0; failed sending one message 748 * result == null, messageId == -1, progres == 100; finish sending batch 749 */ 750 @Override 751 public void sendMailCallback(MessagingException result, long accountId, long messageId, 752 int progress) { 753 if (mListFragment.isOutbox()) { 754 // reset captured error when we start sending one or more messages 755 if (messageId == -1 && result == null && progress == 0) { 756 mSendMessageException = null; 757 } 758 // capture first exception that comes along 759 if (result != null && mSendMessageException == null) { 760 mSendMessageException = result; 761 } 762 // if we're completing the sequence, change the banner state 763 if (messageId == -1 && progress == 100) { 764 updateBanner(mSendMessageException, progress, mListFragment.getMailboxId()); 765 } 766 // always update the spinner, which has less state to worry about 767 updateProgress(result, progress); 768 } 769 } 770 771 private void updateProgress(MessagingException result, int progress) { 772 showProgressIcon(result == null && progress < 100); 773 } 774 775 /** 776 * Show or hide the connection error banner, and convert the various MessagingException 777 * variants into localizable text. There is hysteresis in the show/hide logic: Once shown, 778 * the banner will remain visible until some progress is made on the connection. The 779 * goal is to keep it from flickering during retries in a bad connection state. 780 * 781 * @param result 782 * @param progress 783 */ 784 private void updateBanner(MessagingException result, int progress, long mailboxKey) { 785 if (mailboxKey != mListFragment.getMailboxId()) { 786 return; 787 } 788 if (result != null) { 789 int id = R.string.status_network_error; 790 if (result instanceof AuthenticationFailedException) { 791 id = R.string.account_setup_failed_dlg_auth_message; 792 } else if (result instanceof CertificateValidationException) { 793 id = R.string.account_setup_failed_dlg_certificate_message; 794 } else { 795 switch (result.getExceptionType()) { 796 case MessagingException.IOERROR: 797 id = R.string.account_setup_failed_ioerror; 798 break; 799 case MessagingException.TLS_REQUIRED: 800 id = R.string.account_setup_failed_tls_required; 801 break; 802 case MessagingException.AUTH_REQUIRED: 803 id = R.string.account_setup_failed_auth_required; 804 break; 805 case MessagingException.GENERAL_SECURITY: 806 id = R.string.account_setup_failed_security; 807 break; 808 // TODO Generate a unique string for this case, which is the case 809 // where the security policy needs to be updated. 810 case MessagingException.SECURITY_POLICIES_REQUIRED: 811 id = R.string.account_setup_failed_security; 812 break; 813 } 814 } 815 showErrorBanner(getString(id)); 816 } else if (progress > 0) { 817 showErrorBanner(null); 818 } 819 } 820 } 821} 822