UIControllerOnePane.java revision ecd9c93cfaad7c64201777fa5d62e3a5b6665720
1/* 2 * Copyright (C) 2011 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.Email; 20import com.android.email.MessageListContext; 21import com.android.email.R; 22import com.android.emailcommon.Logging; 23import com.android.emailcommon.provider.Account; 24import com.android.emailcommon.provider.EmailContent.Message; 25import com.android.emailcommon.provider.Mailbox; 26 27import android.app.Activity; 28import android.app.Fragment; 29import android.app.FragmentTransaction; 30import android.os.Bundle; 31import android.util.Log; 32 33import java.util.Set; 34 35 36/** 37 * UI Controller for non x-large devices. Supports a single-pane layout. 38 * 39 * One one-pane, only at most one fragment can be installed at a time. 40 * 41 * Note: Always use {@link #commitFragmentTransaction} to operate fragment transactions, 42 * so that we can easily switch between synchronous and asynchronous transactions. 43 * 44 * Major TODOs 45 * - TODO Newer/Older for message view 46 * - TODO Implement callbacks 47 */ 48class UIControllerOnePane extends UIControllerBase { 49 private static final String BUNDLE_KEY_PREVIOUS_FRAGMENT 50 = "UIControllerOnePane.PREVIOUS_FRAGMENT"; 51 52 // Our custom poor-man's back stack which has only one entry at maximum. 53 private Fragment mPreviousFragment; 54 55 // MailboxListFragment.Callback 56 @Override 57 public void onAccountSelected(long accountId) { 58 // It's from combined view, so "forceShowInbox" doesn't really matter. 59 // (We're always switching accounts.) 60 switchAccount(accountId, true); 61 } 62 63 // MailboxListFragment.Callback 64 @Override 65 public void onMailboxSelected(long accountId, long mailboxId, boolean nestedNavigation) { 66 if (nestedNavigation) { 67 return; // Nothing to do on 1-pane. 68 } 69 openMailbox(accountId, mailboxId); 70 } 71 72 // MailboxListFragment.Callback 73 @Override 74 public void onParentMailboxChanged() { 75 refreshActionBar(); 76 } 77 78 // MessageListFragment.Callback 79 @Override 80 public void onAdvancingOpAccepted(Set<Long> affectedMessages) { 81 // Nothing to do on 1 pane. 82 } 83 84 // MessageListFragment.Callback 85 @Override 86 public void onEnterSelectionMode(boolean enter) { 87 // TODO Auto-generated method stub 88 } 89 90 // MessageListFragment.Callback 91 @Override 92 public void onListLoaded() { 93 // TODO Auto-generated method stub 94 } 95 96 // MessageListFragment.Callback 97 @Override 98 public void onMailboxNotFound() { 99 switchAccount(getUIAccountId(), true); 100 } 101 102 // MessageListFragment.Callback 103 @Override 104 public void onMessageOpen( 105 long messageId, long messageMailboxId, long listMailboxId, int type) { 106 if (type == MessageListFragment.Callback.TYPE_DRAFT) { 107 MessageCompose.actionEditDraft(mActivity, messageId); 108 } else { 109 open(mListContext, messageId); 110 } 111 } 112 113 // MessageListFragment.Callback 114 @Override 115 public boolean onDragStarted() { 116 // No drag&drop on 1-pane 117 return false; 118 } 119 120 // MessageListFragment.Callback 121 @Override 122 public void onDragEnded() { 123 // No drag&drop on 1-pane 124 } 125 126 // MessageViewFragment.Callback 127 @Override 128 public void onForward() { 129 MessageCompose.actionForward(mActivity, getMessageId()); 130 } 131 132 // MessageViewFragment.Callback 133 @Override 134 public void onReply() { 135 MessageCompose.actionReply(mActivity, getMessageId(), false); 136 } 137 138 // MessageViewFragment.Callback 139 @Override 140 public void onReplyAll() { 141 MessageCompose.actionReply(mActivity, getMessageId(), true); 142 } 143 144 // MessageViewFragment.Callback 145 @Override 146 public void onCalendarLinkClicked(long epochEventStartTime) { 147 ActivityHelper.openCalendar(mActivity, epochEventStartTime); 148 } 149 150 // MessageViewFragment.Callback 151 @Override 152 public boolean onUrlInMessageClicked(String url) { 153 return ActivityHelper.openUrlInMessage(mActivity, url, getActualAccountId()); 154 } 155 156 // MessageViewFragment.Callback 157 @Override 158 public void onBeforeMessageGone() { 159 // TODO Auto-generated method stub 160 } 161 162 // MessageViewFragment.Callback 163 @Override 164 public void onMessageSetUnread() { 165 // TODO Auto-generated method stub 166 } 167 168 // MessageViewFragment.Callback 169 @Override 170 public void onRespondedToInvite(int response) { 171 // TODO Auto-generated method stub 172 } 173 174 // MessageViewFragment.Callback 175 @Override 176 public void onLoadMessageError(String errorMessage) { 177 // TODO Auto-generated method stub 178 } 179 180 // MessageViewFragment.Callback 181 @Override 182 public void onLoadMessageFinished() { 183 // TODO Auto-generated method stub 184 } 185 186 // MessageViewFragment.Callback 187 @Override 188 public void onLoadMessageStarted() { 189 // TODO Auto-generated method stub 190 } 191 192 // MessageViewFragment.Callback 193 @Override 194 public void onMessageNotExists() { 195 // TODO Auto-generated method stub 196 } 197 198 // MessageViewFragment.Callback 199 @Override 200 public void onMessageShown() { 201 // TODO Auto-generated method stub 202 } 203 204 // This is all temporary as we'll have a different action bar controller for 1-pane. 205 private class ActionBarControllerCallback implements ActionBarController.Callback { 206 @Override 207 public int getTitleMode() { 208 if (isMailboxListInstalled()) { 209 return TITLE_MODE_ACCOUNT_WITH_ALL_FOLDERS_LABEL; 210 } 211 // TODO Return TITLE_MODE_MESSAGE_SUBJECT if isMessageViewInstalled() 212 return TITLE_MODE_ACCOUNT_WITH_MAILBOX; 213 } 214 215 public String getMessageSubject() { 216 if (isMessageViewInstalled()) { 217 return "TODO: Return current message subject here"; 218 } else { 219 return null; 220 } 221 } 222 223 @Override 224 public boolean shouldShowUp() { 225 return isMessageViewInstalled() 226 || (isMailboxListInstalled() && getMailboxListFragment().canNavigateUp()); 227 } 228 229 @Override 230 public long getUIAccountId() { 231 return UIControllerOnePane.this.getUIAccountId(); 232 } 233 234 @Override 235 public long getMailboxId() { 236 return UIControllerOnePane.this.getMailboxId(); 237 } 238 239 @Override 240 public void onMailboxSelected(long accountId, long mailboxId) { 241 if (mailboxId == Mailbox.NO_MAILBOX) { 242 showAllMailboxes(); 243 } else { 244 openMailbox(accountId, mailboxId); 245 } 246 } 247 248 @Override 249 public boolean isAccountSelected() { 250 return UIControllerOnePane.this.isAccountSelected(); 251 } 252 253 @Override 254 public void onAccountSelected(long accountId) { 255 switchAccount(accountId, true); // Always go to inbox 256 } 257 258 @Override 259 public void onNoAccountsFound() { 260 Welcome.actionStart(mActivity); 261 mActivity.finish(); 262 } 263 264 @Override 265 public void onSearchSubmit(String queryTerm) { 266 if (!isMessageListInstalled()) { 267 return; 268 } 269 UIControllerOnePane.this.onSearchSubmit(queryTerm); 270 } 271 272 @Override 273 public void onSearchExit() { 274 UIControllerOnePane.this.onSearchExit(); 275 } 276 } 277 278 public UIControllerOnePane(EmailActivity activity) { 279 super(activity); 280 } 281 282 @Override 283 protected ActionBarController createActionBarController(Activity activity) { 284 285 // For now, we just reuse the same action bar controller used for 2-pane. 286 // We may change it later. 287 288 return new ActionBarController(activity, activity.getLoaderManager(), 289 activity.getActionBar(), new ActionBarControllerCallback()); 290 } 291 292 @Override 293 public void onSaveInstanceState(Bundle outState) { 294 super.onSaveInstanceState(outState); 295 if (mPreviousFragment != null) { 296 mFragmentManager.putFragment(outState, 297 BUNDLE_KEY_PREVIOUS_FRAGMENT, mPreviousFragment); 298 } 299 } 300 301 @Override 302 public void onRestoreInstanceState(Bundle savedInstanceState) { 303 super.onRestoreInstanceState(savedInstanceState); 304 mPreviousFragment = mFragmentManager.getFragment(savedInstanceState, 305 BUNDLE_KEY_PREVIOUS_FRAGMENT); 306 } 307 308 @Override 309 public int getLayoutId() { 310 return R.layout.email_activity_one_pane; 311 } 312 313 @Override 314 public void onActivityViewReady() { 315 super.onActivityViewReady(); 316 } 317 318 @Override 319 public void onActivityCreated() { 320 super.onActivityCreated(); 321 } 322 323 @Override 324 public long getUIAccountId() { 325 // Get it from the visible fragment. 326 if (isMailboxListInstalled()) { 327 return getMailboxListFragment().getAccountId(); 328 } 329 if (isMessageListInstalled()) { 330 return getMessageListFragment().getAccountId(); 331 } 332 if (isMessageViewInstalled()) { 333 return getMessageViewFragment().getOpenerAccountId(); 334 } 335 return Account.NO_ACCOUNT; 336 } 337 338 private long getMailboxId() { 339 // Get it from the visible fragment. 340 if (isMessageListInstalled()) { 341 return getMessageListFragment().getMailboxId(); 342 } 343 if (isMessageViewInstalled()) { 344 return getMessageViewFragment().getOpenerMailboxId(); 345 } 346 return Mailbox.NO_MAILBOX; 347 } 348 349 @Override 350 public boolean onBackPressed(boolean isSystemBackKey) { 351 if (Email.DEBUG) { 352 // This is VERY important -- no check for DEBUG_LIFECYCLE 353 Log.d(Logging.LOG_TAG, this + " onBackPressed: " + isSystemBackKey); 354 } 355 // Super's method has precedence. Must call it first. 356 if (super.onBackPressed(isSystemBackKey)) { 357 return true; 358 } 359 // If the mailbox list is shown and showing a nested mailbox, let it navigate up first. 360 if (isMailboxListInstalled() && getMailboxListFragment().navigateUp()) { 361 if (DEBUG_FRAGMENTS) { 362 Log.d(Logging.LOG_TAG, this + " Back: back handled by mailbox list"); 363 } 364 return true; 365 } 366 367 // Custom back stack 368 if (shouldPopFromBackStack(isSystemBackKey)) { 369 if (DEBUG_FRAGMENTS) { 370 Log.d(Logging.LOG_TAG, this + " Back: Popping from back stack"); 371 } 372 popFromBackStack(); 373 return true; 374 } 375 376 // No entry in the back stack. 377 // If the message view is shown, show the "parent" message list. 378 // This happens when we get a deep link to a message. (e.g. from a widget) 379 if (isMessageViewInstalled()) { 380 if (DEBUG_FRAGMENTS) { 381 Log.d(Logging.LOG_TAG, this + " Back: Message view -> Message List"); 382 } 383 openMailbox(getMessageViewFragment().getOpenerAccountId(), 384 getMessageViewFragment().getOpenerMailboxId()); 385 return true; 386 } 387 return false; 388 } 389 390 @Override 391 public void openInternal(final MessageListContext listContext, final long messageId) { 392 if (Email.DEBUG) { 393 // This is VERY important -- don't check for DEBUG_LIFECYCLE 394 Log.i(Logging.LOG_TAG, this + " open " + listContext + " messageId=" + messageId); 395 } 396 397 if (messageId != Message.NO_MESSAGE) { 398 long accountId = listContext.mAccountId; 399 long mailboxId = listContext.getMailboxId(); 400 showFragment(MessageViewFragment.newInstance(accountId, mailboxId, messageId)); 401 } else { 402 showFragment(MessageListFragment.newInstance(listContext)); 403 } 404 } 405 406 407 /** 408 * @return currently installed {@link Fragment} (1-pane has only one at most), or null if none 409 * exists. 410 */ 411 private Fragment getInstalledFragment() { 412 if (isMailboxListInstalled()) { 413 return getMailboxListFragment(); 414 } else if (isMessageListInstalled()) { 415 return getMessageListFragment(); 416 } else if (isMessageViewInstalled()) { 417 return getMessageViewFragment(); 418 } 419 return null; 420 } 421 422 /** 423 * Show the mailbox list. 424 * 425 * This is the only way to open the mailbox list on 1-pane. 426 * {@link #open(MessageListContext, long)} will only open either the message list or the 427 * message view. 428 */ 429 private void openMailboxList(long accountId) { 430 setListContext(null); 431 showFragment(MailboxListFragment.newInstance(accountId, Mailbox.NO_MAILBOX, false)); 432 } 433 434 /** 435 * Use this instead of {@link FragmentTransaction#commit}. We may switch to the asynchronous 436 * transaction some day. 437 */ 438 private void commitFragmentTransaction(FragmentTransaction ft) { 439 if (!ft.isEmpty()) { 440 // STOPSHIP Don't use AllowingStateLoss. See b/4519430 441 ft.commitAllowingStateLoss(); 442 mFragmentManager.executePendingTransactions(); 443 } 444 } 445 446 /** 447 * Push the installed fragment into our custom back stack (or optionally 448 * {@link FragmentTransaction#remove} it) and {@link FragmentTransaction#add} {@code fragment}. 449 * 450 * @param fragment {@link Fragment} to be added. 451 * 452 * TODO Delay-call the whole method and use the synchronous transaction. 453 */ 454 private void showFragment(Fragment fragment) { 455 if (DEBUG_FRAGMENTS) { 456 Log.i(Logging.LOG_TAG, this + " backstack: [push] " + getInstalledFragment() 457 + " -> " + fragment); 458 } 459 final FragmentTransaction ft = mFragmentManager.beginTransaction(); 460 if (mPreviousFragment != null) { 461 if (DEBUG_FRAGMENTS) { 462 Log.d(Logging.LOG_TAG, this + " showFragment: destroying previous fragment " 463 + mPreviousFragment); 464 } 465 removeFragment(ft, mPreviousFragment); 466 mPreviousFragment = null; 467 } 468 // Remove the current fragment or push it into the backstack. 469 final Fragment installed = getInstalledFragment(); 470 if (installed != null) { 471 if (installed instanceof MessageViewFragment) { 472 // Message view should never be pushed to the backstack. 473 if (DEBUG_FRAGMENTS) { 474 Log.d(Logging.LOG_TAG, this + " showFragment: removing " + installed); 475 } 476 ft.remove(installed); 477 } else { 478 // Other fragments should be pushed. 479 mPreviousFragment = installed; 480 if (DEBUG_FRAGMENTS) { 481 Log.d(Logging.LOG_TAG, this + " showFragment: detaching " + mPreviousFragment); 482 } 483 ft.detach(mPreviousFragment); 484 } 485 } 486 // Add the new one 487 if (DEBUG_FRAGMENTS) { 488 Log.d(Logging.LOG_TAG, this + " showFragment: adding " + fragment); 489 } 490 ft.add(R.id.fragment_placeholder, fragment); 491 ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN); 492 commitFragmentTransaction(ft); 493 } 494 495 /** 496 * @param isSystemBackKey <code>true</code> if the system back key was pressed. 497 * <code>false</code> if it's caused by the "home" icon click on the action bar. 498 * @return true if we should pop from our custom back stack. 499 */ 500 private boolean shouldPopFromBackStack(boolean isSystemBackKey) { 501 if (mPreviousFragment == null) { 502 return false; // Nothing in the back stack 503 } 504 if (mPreviousFragment instanceof MessageViewFragment) { 505 throw new IllegalStateException("Message view should never be in backstack"); 506 } 507 final Fragment installed = getInstalledFragment(); 508 if (installed == null) { 509 // If no fragment is installed right now, do nothing. 510 return false; 511 } 512 513 // Okay now we have 2 fragments; the one in the back stack and the one that's currently 514 // installed. 515 if (mPreviousFragment.getClass() == installed.getClass()) { 516 // We never want to go back to the same kind of fragment, which happens when the user 517 // is on the message list, and selects another mailbox on the action bar. 518 return false; 519 } 520 521 if (isSystemBackKey) { 522 // In other cases, the system back key should always work. 523 return true; 524 } else { 525 // Home icon press -- there are cases where we don't want it to work. 526 527 // Disallow the Message list <-> mailbox list transition 528 if ((mPreviousFragment instanceof MailboxListFragment) 529 && (installed instanceof MessageListFragment)) { 530 return false; 531 } 532 if ((mPreviousFragment instanceof MessageListFragment) 533 && (installed instanceof MailboxListFragment)) { 534 return false; 535 } 536 return true; 537 } 538 } 539 540 /** 541 * Pop from our custom back stack. 542 * 543 * TODO Delay-call the whole method and use the synchronous transaction. 544 */ 545 private void popFromBackStack() { 546 if (mPreviousFragment == null) { 547 return; 548 } 549 final FragmentTransaction ft = mFragmentManager.beginTransaction(); 550 final Fragment installed = getInstalledFragment(); 551 if (DEBUG_FRAGMENTS) { 552 Log.i(Logging.LOG_TAG, this + " backstack: [pop] " + installed + " -> " 553 + mPreviousFragment); 554 } 555 removeFragment(ft, installed); 556 557 // Restore listContext. 558 if (mPreviousFragment instanceof MailboxListFragment) { 559 setListContext(null); 560 } else if (mPreviousFragment instanceof MessageListFragment) { 561 setListContext(((MessageListFragment) mPreviousFragment).getListContext()); 562 } else { 563 throw new IllegalStateException("Message view should never be in backstack"); 564 } 565 566 ft.attach(mPreviousFragment); 567 ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE); 568 mPreviousFragment = null; 569 commitFragmentTransaction(ft); 570 return; 571 } 572 573 private void showAllMailboxes() { 574 if (!isAccountSelected()) { 575 return; // Can happen because of asynchronous fragment transactions. 576 } 577 578 openMailboxList(getUIAccountId()); 579 } 580 581 /* 582 * STOPSHIP Remove this -- see the base class method. 583 */ 584 @Override 585 public long getMailboxSettingsMailboxId() { 586 return isMessageListInstalled() 587 ? getMessageListFragment().getMailboxId() 588 : Mailbox.NO_MAILBOX; 589 } 590 591 @Override 592 protected boolean canSearch() { 593 return isMessageListInstalled(); 594 } 595 596 @Override 597 protected boolean isRefreshEnabled() { 598 // Refreshable only when an actual account is selected, and message view isn't shown. 599 // (i.e. only available on the mailbox list or the message view, but not on the combined 600 // one) 601 return isActualAccountSelected() && !isMessageViewInstalled(); 602 } 603 604 @Override 605 public void onRefresh() { 606 if (!isRefreshEnabled()) { 607 return; 608 } 609 if (isMessageListInstalled()) { 610 mRefreshManager.refreshMessageList(getActualAccountId(), getMailboxId(), true); 611 } else { 612 mRefreshManager.refreshMailboxList(getActualAccountId()); 613 } 614 } 615 616 @Override 617 protected boolean isRefreshInProgress() { 618 if (!isRefreshEnabled()) { 619 return false; 620 } 621 if (isMessageListInstalled()) { 622 return mRefreshManager.isMessageListRefreshing(getMailboxId()); 623 } else { 624 return mRefreshManager.isMailboxListRefreshing(getActualAccountId()); 625 } 626 } 627} 628