EmailActivity.java revision f29221efc921b91ffbe89a790277d4ef56389749
1/* 2 * Copyright (C) 2010 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 android.app.Activity; 20import android.app.Fragment; 21import android.content.Intent; 22import android.os.Bundle; 23import android.os.Handler; 24import android.text.TextUtils; 25import android.util.Log; 26import android.view.Menu; 27import android.view.MenuItem; 28import android.view.View; 29import android.widget.TextView; 30 31import com.android.email.Controller; 32import com.android.email.ControllerResultUiThreadWrapper; 33import com.android.email.Email; 34import com.android.email.MessageListContext; 35import com.android.email.MessagingExceptionStrings; 36import com.android.email.R; 37import com.android.emailcommon.Logging; 38import com.android.emailcommon.mail.MessagingException; 39import com.android.emailcommon.provider.Account; 40import com.android.emailcommon.provider.EmailContent.Message; 41import com.android.emailcommon.provider.Mailbox; 42import com.android.emailcommon.utility.EmailAsyncTask; 43import com.google.common.base.Preconditions; 44 45import java.util.ArrayList; 46 47/** 48 * The main Email activity, which is used on both the tablet and the phone. 49 * 50 * Because this activity is device agnostic, so most of the UI aren't owned by this, but by 51 * the UIController. 52 */ 53public class EmailActivity extends Activity implements View.OnClickListener, FragmentInstallable { 54 public static final String EXTRA_ACCOUNT_ID = "ACCOUNT_ID"; 55 public static final String EXTRA_MAILBOX_ID = "MAILBOX_ID"; 56 public static final String EXTRA_MESSAGE_ID = "MESSAGE_ID"; 57 public static final String EXTRA_QUERY_STRING = "QUERY_STRING"; 58 59 /** Loader IDs starting with this is safe to use from UIControllers. */ 60 static final int UI_CONTROLLER_LOADER_ID_BASE = 100; 61 62 /** Loader IDs starting with this is safe to use from ActionBarController. */ 63 static final int ACTION_BAR_CONTROLLER_LOADER_ID_BASE = 200; 64 65 private static final int MAILBOX_SYNC_FREQUENCY_DIALOG = 1; 66 private static final int MAILBOX_SYNC_LOOKBACK_DIALOG = 2; 67 68 private Controller mController; 69 private Controller.Result mControllerResult; 70 71 private UIControllerBase mUIController; 72 73 private final EmailAsyncTask.Tracker mTaskTracker = new EmailAsyncTask.Tracker(); 74 75 /** Banner to display errors */ 76 private BannerController mErrorBanner; 77 /** Id of the account that had a messaging exception most recently. */ 78 private long mLastErrorAccountId; 79 80 /** 81 * Create an intent to launch and open account's inbox. 82 * 83 * @param accountId If -1, default account will be used. 84 */ 85 public static Intent createOpenAccountIntent(Activity fromActivity, long accountId) { 86 Intent i = IntentUtilities.createRestartAppIntent(fromActivity, EmailActivity.class); 87 if (accountId != -1) { 88 i.putExtra(EXTRA_ACCOUNT_ID, accountId); 89 } 90 return i; 91 } 92 93 /** 94 * Create an intent to launch and open a mailbox. 95 * 96 * @param accountId must not be -1. 97 * @param mailboxId must not be -1. Magic mailboxes IDs (such as 98 * {@link Mailbox#QUERY_ALL_INBOXES}) don't work. 99 */ 100 public static Intent createOpenMailboxIntent(Activity fromActivity, long accountId, 101 long mailboxId) { 102 if (accountId == -1 || mailboxId == -1) { 103 throw new IllegalArgumentException(); 104 } 105 Intent i = IntentUtilities.createRestartAppIntent(fromActivity, EmailActivity.class); 106 i.putExtra(EXTRA_ACCOUNT_ID, accountId); 107 i.putExtra(EXTRA_MAILBOX_ID, mailboxId); 108 return i; 109 } 110 111 /** 112 * Create an intent to launch and open a message. 113 * 114 * @param accountId must not be -1. 115 * @param mailboxId must not be -1. Magic mailboxes IDs (such as 116 * {@link Mailbox#QUERY_ALL_INBOXES}) don't work. 117 * @param messageId must not be -1. 118 */ 119 public static Intent createOpenMessageIntent(Activity fromActivity, long accountId, 120 long mailboxId, long messageId) { 121 if (accountId == -1 || mailboxId == -1 || messageId == -1) { 122 throw new IllegalArgumentException(); 123 } 124 Intent i = IntentUtilities.createRestartAppIntent(fromActivity, EmailActivity.class); 125 i.putExtra(EXTRA_ACCOUNT_ID, accountId); 126 i.putExtra(EXTRA_MAILBOX_ID, mailboxId); 127 i.putExtra(EXTRA_MESSAGE_ID, messageId); 128 return i; 129 } 130 131 /** 132 * Create an intent to launch search activity. 133 * 134 * @param accountId ID of the account for the mailbox. Must not be {@link Account#NO_ACCOUNT}. 135 * @param mailboxId ID of the mailbox to search, or {@link Mailbox#NO_MAILBOX} to perform 136 * global search. 137 * @param query query string. 138 */ 139 public static Intent createSearchIntent(Activity fromActivity, long accountId, 140 long mailboxId, String query) { 141 Preconditions.checkArgument(Account.isNormalAccount(accountId), 142 "Can only search in normal accounts"); 143 144 // Note that a search doesn't use a restart intent, as we want another instance of 145 // the activity to sit on the stack for search. 146 Intent i = new Intent(fromActivity, EmailActivity.class); 147 i.putExtra(EXTRA_ACCOUNT_ID, accountId); 148 i.putExtra(EXTRA_MAILBOX_ID, mailboxId); 149 i.putExtra(EXTRA_QUERY_STRING, query); 150 i.setAction(Intent.ACTION_SEARCH); 151 return i; 152 } 153 154 /** 155 * Initialize {@link #mUIController}. 156 */ 157 private void initUIController() { 158 mUIController = UiUtilities.useTwoPane(this) 159 ? new UIControllerTwoPane(this) : new UIControllerOnePane(this); 160 } 161 162 @Override 163 protected void onCreate(Bundle savedInstanceState) { 164 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Logging.LOG_TAG, this + " onCreate"); 165 166 // UIController is used in onPrepareOptionsMenu(), which can be called from within 167 // super.onCreate(), so we need to initialize it here. 168 initUIController(); 169 170 super.onCreate(savedInstanceState); 171 ActivityHelper.debugSetWindowFlags(this); 172 setContentView(mUIController.getLayoutId()); 173 174 mUIController.onActivityViewReady(); 175 176 mController = Controller.getInstance(this); 177 mControllerResult = new ControllerResultUiThreadWrapper<ControllerResult>(new Handler(), 178 new ControllerResult()); 179 mController.addResultCallback(mControllerResult); 180 181 // Set up views 182 // TODO Probably better to extract mErrorMessageView related code into a separate class, 183 // so that it'll be easy to reuse for the phone activities. 184 TextView errorMessage = (TextView) findViewById(R.id.error_message); 185 errorMessage.setOnClickListener(this); 186 int errorBannerHeight = getResources().getDimensionPixelSize(R.dimen.error_message_height); 187 mErrorBanner = new BannerController(this, errorMessage, errorBannerHeight); 188 189 if (savedInstanceState != null) { 190 mUIController.onRestoreInstanceState(savedInstanceState); 191 } else { 192 initFromIntent(); 193 } 194 mUIController.onActivityCreated(); 195 } 196 197 private void initFromIntent() { 198 final Intent intent = getIntent(); 199 final MessageListContext viewContext = MessageListContext.forIntent(this, intent); 200 final long messageId = intent.getLongExtra(EXTRA_MESSAGE_ID, Message.NO_MESSAGE); 201 202 mUIController.open(viewContext, messageId); 203 } 204 205 @Override 206 protected void onSaveInstanceState(Bundle outState) { 207 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 208 Log.d(Logging.LOG_TAG, this + " onSaveInstanceState"); 209 } 210 super.onSaveInstanceState(outState); 211 mUIController.onSaveInstanceState(outState); 212 } 213 214 // FragmentInstallable 215 @Override 216 public void onInstallFragment(Fragment fragment) { 217 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 218 Log.d(Logging.LOG_TAG, this + " onInstallFragment fragment=" + fragment); 219 } 220 mUIController.onInstallFragment(fragment); 221 } 222 223 // FragmentInstallable 224 @Override 225 public void onUninstallFragment(Fragment fragment) { 226 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 227 Log.d(Logging.LOG_TAG, this + " onUninstallFragment fragment=" + fragment); 228 } 229 mUIController.onUninstallFragment(fragment); 230 } 231 232 @Override 233 protected void onStart() { 234 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Logging.LOG_TAG, this + " onStart"); 235 super.onStart(); 236 mUIController.onActivityStart(); 237 } 238 239 @Override 240 protected void onResume() { 241 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Logging.LOG_TAG, this + " onResume"); 242 super.onResume(); 243 mUIController.onActivityResume(); 244 /** 245 * In {@link MessageList#onResume()}, we go back to {@link Welcome} if an account 246 * has been added/removed. We don't need to do that here, because we fetch the most 247 * up-to-date account list. Additionally, we detect and do the right thing if all 248 * of the accounts have been removed. 249 */ 250 } 251 252 @Override 253 protected void onPause() { 254 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Logging.LOG_TAG, this + " onPause"); 255 super.onPause(); 256 mUIController.onActivityPause(); 257 } 258 259 @Override 260 protected void onStop() { 261 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Logging.LOG_TAG, this + " onStop"); 262 super.onStop(); 263 mUIController.onActivityStop(); 264 } 265 266 @Override 267 protected void onDestroy() { 268 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Logging.LOG_TAG, this + " onDestroy"); 269 mController.removeResultCallback(mControllerResult); 270 mTaskTracker.cancellAllInterrupt(); 271 mUIController.onActivityDestroy(); 272 super.onDestroy(); 273 } 274 275 @Override 276 public void onBackPressed() { 277 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 278 Log.d(Logging.LOG_TAG, this + " onBackPressed"); 279 } 280 if (!mUIController.onBackPressed(true)) { 281 // Not handled by UIController -- perform the default. i.e. close the app. 282 super.onBackPressed(); 283 } 284 } 285 286 @Override 287 public void onClick(View v) { 288 switch (v.getId()) { 289 case R.id.error_message: 290 dismissErrorMessage(); 291 break; 292 } 293 } 294 295 /** 296 * Force dismiss the error banner. 297 */ 298 private void dismissErrorMessage() { 299 mErrorBanner.dismiss(); 300 } 301 302 @Override 303 public boolean onCreateOptionsMenu(Menu menu) { 304 return mUIController.onCreateOptionsMenu(getMenuInflater(), menu); 305 } 306 307 @Override 308 public boolean onPrepareOptionsMenu(Menu menu) { 309 return mUIController.onPrepareOptionsMenu(getMenuInflater(), menu); 310 } 311 312 /** 313 * Called when the search key is pressd. 314 * 315 * Use the below command to emulate the key press on devices without the search key. 316 * adb shell input keyevent 84 317 */ 318 @Override 319 public boolean onSearchRequested() { 320 if (Email.DEBUG) { 321 Log.d(Logging.LOG_TAG, this + " onSearchRequested"); 322 } 323 mUIController.onSearchRequested(); 324 return true; // Event handled. 325 } 326 327 @Override 328 @SuppressWarnings("deprecation") 329 public boolean onOptionsItemSelected(MenuItem item) { 330 if (mUIController.onOptionsItemSelected(item)) { 331 return true; 332 } 333 return super.onOptionsItemSelected(item); 334 } 335 336 /** 337 * A {@link Controller.Result} to detect connection status. 338 */ 339 private class ControllerResult extends Controller.Result { 340 @Override 341 public void sendMailCallback( 342 MessagingException result, long accountId, long messageId, int progress) { 343 handleError(result, accountId, progress); 344 } 345 346 @Override 347 public void serviceCheckMailCallback( 348 MessagingException result, long accountId, long mailboxId, int progress, long tag) { 349 handleError(result, accountId, progress); 350 } 351 352 @Override 353 public void updateMailboxCallback(MessagingException result, long accountId, long mailboxId, 354 int progress, int numNewMessages, ArrayList<Long> addedMessages) { 355 handleError(result, accountId, progress); 356 } 357 358 @Override 359 public void updateMailboxListCallback( 360 MessagingException result, long accountId, int progress) { 361 handleError(result, accountId, progress); 362 } 363 364 @Override 365 public void loadAttachmentCallback(MessagingException result, long accountId, 366 long messageId, long attachmentId, int progress) { 367 handleError(result, accountId, progress); 368 } 369 370 @Override 371 public void loadMessageForViewCallback(MessagingException result, long accountId, 372 long messageId, int progress) { 373 handleError(result, accountId, progress); 374 } 375 376 private void handleError(final MessagingException result, final long accountId, 377 int progress) { 378 if (accountId == -1) { 379 return; 380 } 381 if (result == null) { 382 if (progress > 0) { 383 // Connection now working; clear the error message banner 384 if (mLastErrorAccountId == accountId) { 385 dismissErrorMessage(); 386 } 387 } 388 } else { 389 Account account = Account.restoreAccountWithId(EmailActivity.this, accountId); 390 if (account == null) return; 391 String message = 392 MessagingExceptionStrings.getErrorString(EmailActivity.this, result); 393 if (!TextUtils.isEmpty(account.mDisplayName)) { 394 // TODO Use properly designed layout. Don't just concatenate strings; 395 // which is generally poor for I18N. 396 message = message + " (" + account.mDisplayName + ")"; 397 } 398 if (mErrorBanner.show(message)) { 399 mLastErrorAccountId = accountId; 400 } 401 } 402 } 403 } 404} 405