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