MessageList.java revision 9e53322ee7b2a46cb762898845303cccec88fb50
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.MessagingExceptionStrings; 23import com.android.email.R; 24import com.android.email.activity.setup.AccountSecurity; 25import com.android.email.activity.setup.AccountSettingsXL; 26import com.android.emailcommon.mail.MessagingException; 27import com.android.emailcommon.provider.EmailContent.Account; 28import com.android.emailcommon.provider.EmailContent.Mailbox; 29 30import android.app.Activity; 31import android.content.Context; 32import android.content.Intent; 33import android.net.Uri; 34import android.os.Bundle; 35import android.os.Handler; 36import android.view.Menu; 37import android.view.MenuItem; 38import android.view.View; 39import android.view.animation.AnimationUtils; 40import android.widget.TextView; 41 42public class MessageList extends Activity implements MessageListFragment.Callback { 43 // Intent extras (internal to this activity) 44 private static final String EXTRA_ACCOUNT_ID = "com.android.email.activity._ACCOUNT_ID"; 45 private static final String EXTRA_MAILBOX_TYPE = "com.android.email.activity.MAILBOX_TYPE"; 46 private static final String EXTRA_MAILBOX_ID = "com.android.email.activity.MAILBOX_ID"; 47 48 private static final int REQUEST_SECURITY = 0; 49 50 // UI support 51 private MessageListFragment mListFragment; 52 private TextView mErrorBanner; 53 54 private final Controller mController = Controller.getInstance(getApplication()); 55 private ControllerResultUiThreadWrapper<ControllerResults> mControllerCallback; 56 57 private MailboxFinder mMailboxFinder; 58 private final MailboxFinderCallback mMailboxFinderCallback = new MailboxFinderCallback(); 59 60 /* package */ MessageListFragment getListFragmentForTest() { 61 return mListFragment; 62 } 63 64 /** 65 * Open a specific mailbox. 66 * 67 * TODO This should just shortcut to a more generic version that can accept a list of 68 * accounts/mailboxes (e.g. merged inboxes). 69 * 70 * @param context 71 * @param id mailbox key 72 */ 73 public static void actionHandleMailbox(Context context, long id) { 74 context.startActivity(createIntent(context, -1, id, -1)); 75 } 76 77 /** 78 * Open a specific mailbox by account & type 79 * 80 * @param context The caller's context (for generating an intent) 81 * @param accountId The account to open 82 * @param mailboxType the type of mailbox to open (e.g. @see EmailContent.Mailbox.TYPE_INBOX) 83 */ 84 public static void actionHandleAccount(Context context, long accountId, int mailboxType) { 85 context.startActivity(createIntent(context, accountId, -1, mailboxType)); 86 } 87 88 /** 89 * Open the inbox of the account with a UUID. It's used to handle old style 90 * (Android <= 1.6) desktop shortcut intents. 91 */ 92 public static void actionOpenAccountInboxUuid(Context context, String accountUuid) { 93 Intent i = createIntent(context, -1, -1, Mailbox.TYPE_INBOX); 94 i.setData(Account.getShortcutSafeUriFromUuid(accountUuid)); 95 context.startActivity(i); 96 } 97 98 /** 99 * Return an intent to open a specific mailbox by account & type. 100 * 101 * @param context The caller's context (for generating an intent) 102 * @param accountId The account to open, or -1 103 * @param mailboxId the ID of the mailbox to open, or -1 104 * @param mailboxType the type of mailbox to open (e.g. @see Mailbox.TYPE_INBOX) or -1 105 */ 106 public static Intent createIntent(Context context, long accountId, long mailboxId, 107 int mailboxType) { 108 Intent intent = new Intent(context, MessageList.class); 109 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 110 if (accountId != -1) intent.putExtra(EXTRA_ACCOUNT_ID, accountId); 111 if (mailboxId != -1) intent.putExtra(EXTRA_MAILBOX_ID, mailboxId); 112 if (mailboxType != -1) intent.putExtra(EXTRA_MAILBOX_TYPE, mailboxType); 113 return intent; 114 } 115 116 /** 117 * Create and return an intent for a desktop shortcut for an account. 118 * 119 * @param context Calling context for building the intent 120 * @param account The account of interest 121 * @param mailboxType The folder name to open (typically Mailbox.TYPE_INBOX) 122 * @return an Intent which can be used to view that account 123 */ 124 public static Intent createAccountIntentForShortcut(Context context, Account account, 125 int mailboxType) { 126 Intent i = createIntent(context, -1, -1, mailboxType); 127 i.setData(account.getShortcutSafeUri()); 128 return i; 129 } 130 131 @Override 132 public void onCreate(Bundle icicle) { 133 super.onCreate(icicle); 134 ActivityHelper.debugSetWindowFlags(this); 135 setContentView(R.layout.message_list); 136 137 mControllerCallback = new ControllerResultUiThreadWrapper<ControllerResults>( 138 new Handler(), new ControllerResults()); 139 mListFragment = (MessageListFragment) getFragmentManager() 140 .findFragmentById(R.id.message_list_fragment); 141 mErrorBanner = (TextView) findViewById(R.id.connection_error_text); 142 143 mListFragment.setCallback(this); 144 145 // Show the appropriate account/mailbox specified by an {@link Intent}. 146 selectAccountAndMailbox(getIntent()); 147 } 148 149 /** 150 * Show the appropriate account/mailbox specified by an {@link Intent}. 151 */ 152 private void selectAccountAndMailbox(Intent intent) { 153 long mailboxId = intent.getLongExtra(EXTRA_MAILBOX_ID, -1); 154 if (mailboxId != -1) { 155 mListFragment.openMailbox(mailboxId); 156 } else { 157 int mailboxType = intent.getIntExtra(EXTRA_MAILBOX_TYPE, Mailbox.TYPE_INBOX); 158 Uri uri = intent.getData(); 159 // TODO Possible ANR. getAccountIdFromShortcutSafeUri accesses DB. 160 long accountId = (uri == null) ? -1 161 : Account.getAccountIdFromShortcutSafeUri(this, uri); 162 if (accountId == -1) { 163 accountId = intent.getLongExtra(EXTRA_ACCOUNT_ID, -1); 164 } 165 if (accountId == -1) { 166 launchWelcomeAndFinish(); 167 return; 168 } 169 mMailboxFinder = new MailboxFinder(this, accountId, mailboxType, 170 mMailboxFinderCallback); 171 mMailboxFinder.startLookup(); 172 } 173 } 174 175 @Override 176 public void onPause() { 177 super.onPause(); 178 mController.removeResultCallback(mControllerCallback); 179 } 180 181 @Override 182 public void onResume() { 183 super.onResume(); 184 mController.addResultCallback(mControllerCallback); 185 186 // Exit immediately if the accounts list has changed (e.g. externally deleted) 187 if (Email.getNotifyUiAccountsChanged()) { 188 Welcome.actionStart(this); 189 finish(); 190 return; 191 } 192 } 193 194 @Override 195 protected void onDestroy() { 196 super.onDestroy(); 197 198 if (mMailboxFinder != null) { 199 mMailboxFinder.cancel(); 200 mMailboxFinder = null; 201 } 202 } 203 204 205 private void launchWelcomeAndFinish() { 206 Welcome.actionStart(this); 207 finish(); 208 } 209 210 /** 211 * Called when the list fragment can't find mailbox/account. 212 */ 213 public void onMailboxNotFound() { 214 finish(); 215 } 216 217 @Override 218 public void onMessageOpen(long messageId, long messageMailboxId, long listMailboxId, int type) { 219 if (type == MessageListFragment.Callback.TYPE_DRAFT) { 220 MessageCompose.actionEditDraft(this, messageId); 221 } else { 222 // WARNING: here we pass "listMailboxId", which can be the negative id of 223 // a compound mailbox, instead of the mailboxId of the particular message that 224 // is opened. This is to support the next/prev buttons on the message view 225 // properly even for combined mailboxes. 226 MessageView.actionView(this, messageId, listMailboxId); 227 } 228 } 229 230 @Override 231 public void onEnterSelectionMode(boolean enter) { 232 } 233 234 @Override 235 public boolean onCreateOptionsMenu(Menu menu) { 236 getMenuInflater().inflate(R.menu.message_list_option, menu); 237 return true; 238 } 239 240 @Override 241 public boolean onPrepareOptionsMenu(Menu menu) { 242 // TODO Disable "refresh" for combined mailboxes 243 return true; 244 } 245 246 @Override 247 public boolean onOptionsItemSelected(MenuItem item) { 248 switch (item.getItemId()) { 249 case R.id.refresh: 250 mListFragment.onRefresh(true); 251 return true; 252 case R.id.folders: 253 onFolders(); 254 return true; 255 case R.id.accounts: 256 onAccounts(); 257 return true; 258 case R.id.compose: 259 onCompose(); 260 return true; 261 case R.id.account_settings: 262 onEditAccount(); 263 return true; 264 default: 265 return super.onOptionsItemSelected(item); 266 } 267 } 268 269 private void onFolders() { 270 if (!mListFragment.isMagicMailbox()) { // Magic boxes don't have "folders" option. 271 // TODO smaller projection 272 Mailbox mailbox = Mailbox.restoreMailboxWithId(this, mListFragment.getMailboxId()); 273 if (mailbox != null) { 274 MailboxList.actionHandleAccount(this, mailbox.mAccountKey); 275 finish(); 276 } 277 } 278 } 279 280 private void onAccounts() { 281 AccountFolderList.actionShowAccounts(this); 282 finish(); 283 } 284 285 private void onCompose() { 286 MessageCompose.actionCompose(this, mListFragment.getAccountId()); 287 } 288 289 private void onEditAccount() { 290 AccountSettingsXL.actionSettings(this, mListFragment.getAccountId()); 291 } 292 293 /** 294 * Handle the eventual result from the security update activity 295 * 296 * Note, this is extremely coarse, and it simply returns the user to the Accounts list. 297 * Anything more requires refactoring of this Activity. 298 */ 299 @Override 300 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 301 switch (requestCode) { 302 case REQUEST_SECURITY: 303 onAccounts(); 304 } 305 super.onActivityResult(requestCode, resultCode, data); 306 } 307 308 private void showProgressIcon(boolean show) { 309 // TODO Show "refreshing" icon somewhere. (It's on the action bar on xlarge.) 310 } 311 312 private void showErrorBanner(String message) { 313 boolean isVisible = mErrorBanner.getVisibility() == View.VISIBLE; 314 if (message != null) { 315 mErrorBanner.setText(message); 316 if (!isVisible) { 317 mErrorBanner.setVisibility(View.VISIBLE); 318 mErrorBanner.startAnimation( 319 AnimationUtils.loadAnimation( 320 MessageList.this, R.anim.header_appear)); 321 } 322 } else { 323 if (isVisible) { 324 mErrorBanner.setVisibility(View.GONE); 325 mErrorBanner.startAnimation( 326 AnimationUtils.loadAnimation( 327 MessageList.this, R.anim.header_disappear)); 328 } 329 } 330 } 331 332 /** 333 * TODO This should probably be removed -- use RefreshManager instead to update the progress 334 * icon and the error banner. 335 * 336 * Controller results listener. We wrap it with {@link ControllerResultUiThreadWrapper}, 337 * so all methods are called on the UI thread. 338 */ 339 private class ControllerResults extends Controller.Result { 340 341 // This is used to alter the connection banner operation for sending messages 342 private MessagingException mSendMessageException; 343 344 // TODO check accountKey and only react to relevant notifications 345 @Override 346 public void updateMailboxCallback(MessagingException result, long accountKey, 347 long mailboxKey, int progress, int numNewMessages) { 348 updateBanner(result, progress, mailboxKey); 349 updateProgress(result, progress); 350 } 351 352 /** 353 * We alter the updateBanner hysteresis here to capture any failures and handle 354 * them just once at the end. This callback is overly overloaded: 355 * result == null, messageId == -1, progress == 0: start batch send 356 * result == null, messageId == xx, progress == 0: start sending one message 357 * result == xxxx, messageId == xx, progress == 0; failed sending one message 358 * result == null, messageId == -1, progres == 100; finish sending batch 359 */ 360 @Override 361 public void sendMailCallback(MessagingException result, long accountId, long messageId, 362 int progress) { 363 if (mListFragment.isOutbox()) { 364 // reset captured error when we start sending one or more messages 365 if (messageId == -1 && result == null && progress == 0) { 366 mSendMessageException = null; 367 } 368 // capture first exception that comes along 369 if (result != null && mSendMessageException == null) { 370 mSendMessageException = result; 371 } 372 // if we're completing the sequence, change the banner state 373 if (messageId == -1 && progress == 100) { 374 updateBanner(mSendMessageException, progress, mListFragment.getMailboxId()); 375 } 376 // always update the spinner, which has less state to worry about 377 updateProgress(result, progress); 378 } 379 } 380 381 private void updateProgress(MessagingException result, int progress) { 382 showProgressIcon(result == null && progress < 100); 383 } 384 385 /** 386 * Show or hide the connection error banner, and convert the various MessagingException 387 * variants into localizable text. There is hysteresis in the show/hide logic: Once shown, 388 * the banner will remain visible until some progress is made on the connection. The 389 * goal is to keep it from flickering during retries in a bad connection state. 390 * 391 * @param result 392 * @param progress 393 */ 394 private void updateBanner(MessagingException result, int progress, long mailboxKey) { 395 if (mailboxKey != mListFragment.getMailboxId()) { 396 return; 397 } 398 if (result != null) { 399 showErrorBanner( 400 MessagingExceptionStrings.getErrorString(MessageList.this, result)); 401 } else if (progress > 0) { 402 showErrorBanner(null); 403 } 404 } 405 } 406 407 private class MailboxFinderCallback implements MailboxFinder.Callback { 408 @Override 409 public void onMailboxFound(long accountId, long mailboxId) { 410 mListFragment.openMailbox(mailboxId); 411 } 412 413 @Override 414 public void onAccountNotFound() { 415 // Let the Welcome activity show the default screen. 416 launchWelcomeAndFinish(); 417 } 418 419 @Override 420 public void onMailboxNotFound(long accountId) { 421 // Let the Welcome activity show the default screen. 422 launchWelcomeAndFinish(); 423 } 424 425 @Override 426 public void onAccountSecurityHold(long accountId) { 427 // launch the security setup activity 428 Intent i = AccountSecurity.actionUpdateSecurityIntent( 429 MessageList.this, accountId, true); 430 MessageList.this.startActivityForResult(i, REQUEST_SECURITY); 431 } 432 } 433} 434