TwoPaneController.java revision 859822be95f6ff23ada95e3b93504da82b835082
1/******************************************************************************* 2 * Copyright (C) 2012 Google Inc. 3 * Licensed to The Android Open Source Project. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 *******************************************************************************/ 17 18package com.android.mail.ui; 19 20import android.app.Activity; 21import android.app.Fragment; 22import android.app.FragmentManager; 23import android.app.FragmentTransaction; 24import android.content.Intent; 25import android.os.Bundle; 26import android.support.annotation.IdRes; 27import android.support.annotation.LayoutRes; 28import android.support.v7.app.ActionBar; 29import android.view.KeyEvent; 30import android.view.View; 31import android.widget.ImageView; 32import android.widget.ListView; 33 34import com.android.mail.ConversationListContext; 35import com.android.mail.R; 36import com.android.mail.providers.Account; 37import com.android.mail.providers.Conversation; 38import com.android.mail.providers.Folder; 39import com.android.mail.providers.UIProvider.ConversationListIcon; 40import com.android.mail.utils.EmptyStateUtils; 41import com.android.mail.utils.LogUtils; 42import com.android.mail.utils.Utils; 43import com.google.common.collect.Lists; 44 45import java.util.List; 46 47/** 48 * Controller for two-pane Mail activity. Two Pane is used for tablets, where screen real estate 49 * abounds. 50 */ 51public final class TwoPaneController extends AbstractActivityController implements 52 ConversationViewFrame.DownEventListener { 53 54 private static final String SAVED_MISCELLANEOUS_VIEW = "saved-miscellaneous-view"; 55 private static final String SAVED_MISCELLANEOUS_VIEW_TRANSACTION_ID = 56 "saved-miscellaneous-view-transaction-id"; 57 58 private TwoPaneLayout mLayout; 59 private ImageView mEmptyCvView; 60 private List<TwoPaneLayout.ConversationListLayoutListener> mConversationListLayoutListeners = 61 Lists.newArrayList(); 62 @Deprecated 63 private Conversation mConversationToShow; 64 65 /** 66 * 2-pane, in wider configurations, allows peeking at a conversation view without having the 67 * conversation marked-as-read as far as read/unread state goes.<br> 68 * <br> 69 * This flag applies to {@link AbstractActivityController#mCurrentConversation} and indicates 70 * that the current conversation, if set, is in a 'peeking' state. If there is no current 71 * conversation, peeking is implied (in certain view configurations) and this value is 72 * meaningless. 73 */ 74 // TODO: save in instance state 75 private boolean mCurrentConversationJustPeeking; 76 77 // For peeking conversations, we'll put it in a separate runnable. 78 private static final int PEEK_CONVERSATION_DELAY_MS = 500; 79 private final Runnable mPeekConversationRunnable = new Runnable() { 80 @Override 81 public void run() { 82 if (!mActivity.isFinishing()) { 83 showCurrentConversationInPager(); 84 } 85 } 86 }; 87 88 /** 89 * Used to determine whether onViewModeChanged should skip a potential 90 * fragment transaction that would remove a miscellaneous view. 91 */ 92 private boolean mSavedMiscellaneousView = false; 93 94 private boolean mIsTabletLandscape; 95 96 public TwoPaneController(MailActivity activity, ViewMode viewMode) { 97 super(activity, viewMode); 98 } 99 100 @Override 101 public boolean isCurrentConversationJustPeeking() { 102 return mCurrentConversationJustPeeking; 103 } 104 105 private boolean isHidingConversationList() { 106 return (mViewMode.isConversationMode() || mViewMode.isAdMode()) && 107 !mLayout.shouldShowPreviewPanel(); 108 } 109 110 /** 111 * Display the conversation list fragment. 112 */ 113 private void initializeConversationListFragment() { 114 if (Intent.ACTION_SEARCH.equals(mActivity.getIntent().getAction())) { 115 if (shouldEnterSearchConvMode()) { 116 mViewMode.enterSearchResultsConversationMode(); 117 } else { 118 mViewMode.enterSearchResultsListMode(); 119 } 120 } 121 renderConversationList(); 122 } 123 124 /** 125 * Render the conversation list in the correct pane. 126 */ 127 private void renderConversationList() { 128 if (mActivity == null) { 129 return; 130 } 131 FragmentTransaction fragmentTransaction = mActivity.getFragmentManager().beginTransaction(); 132 // Use cross fading animation. 133 fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); 134 final ConversationListFragment conversationListFragment = 135 ConversationListFragment.newInstance(mConvListContext); 136 fragmentTransaction.replace(R.id.conversation_list_place_holder, conversationListFragment, 137 TAG_CONVERSATION_LIST); 138 fragmentTransaction.commitAllowingStateLoss(); 139 // Set default navigation here once the ConversationListFragment is created. 140 conversationListFragment.setNextFocusStartId( 141 getClfNextFocusStartId()); 142 } 143 144 @Override 145 public boolean doesActionChangeConversationListVisibility(final int action) { 146 if (action == R.id.settings 147 || action == R.id.compose 148 || action == R.id.help_info_menu_item 149 || action == R.id.feedback_menu_item) { 150 return true; 151 } 152 153 return false; 154 } 155 156 @Override 157 protected boolean isConversationListVisible() { 158 return !mLayout.isConversationListCollapsed(); 159 } 160 161 @Override 162 protected void showConversationList(ConversationListContext listContext) { 163 initializeConversationListFragment(); 164 } 165 166 @Override 167 public @LayoutRes int getContentViewResource() { 168 return R.layout.two_pane_activity; 169 } 170 171 @Override 172 public boolean onCreate(Bundle savedState) { 173 mLayout = (TwoPaneLayout) mActivity.findViewById(R.id.two_pane_activity); 174 mEmptyCvView = (ImageView) mActivity.findViewById(R.id.conversation_pane_no_message_view); 175 if (mLayout == null) { 176 // We need the layout for everything. Crash/Return early if it is null. 177 LogUtils.wtf(LOG_TAG, "mLayout is null!"); 178 return false; 179 } 180 mLayout.setController(this); 181 mActivity.getWindow().setBackgroundDrawable(null); 182 mIsTabletLandscape = mActivity.getResources().getBoolean(R.bool.is_tablet_landscape); 183 184 final FolderListFragment flf = getFolderListFragment(); 185 flf.setMiniDrawerEnabled(true); 186 flf.setMinimized(true); 187 188 if (savedState != null) { 189 mSavedMiscellaneousView = savedState.getBoolean(SAVED_MISCELLANEOUS_VIEW, false); 190 mMiscellaneousViewTransactionId = 191 savedState.getInt(SAVED_MISCELLANEOUS_VIEW_TRANSACTION_ID, -1); 192 } 193 194 // 2-pane layout is the main listener of view mode changes, and issues secondary 195 // notifications upon animation completion: 196 // (onConversationVisibilityChanged, onConversationListVisibilityChanged) 197 mViewMode.addListener(mLayout); 198 199 return super.onCreate(savedState); 200 } 201 202 @Override 203 public void onDestroy() { 204 super.onDestroy(); 205 mHandler.removeCallbacks(mPeekConversationRunnable); 206 } 207 208 @Override 209 public void onSaveInstanceState(Bundle outState) { 210 super.onSaveInstanceState(outState); 211 212 outState.putBoolean(SAVED_MISCELLANEOUS_VIEW, mMiscellaneousViewTransactionId >= 0); 213 outState.putInt(SAVED_MISCELLANEOUS_VIEW_TRANSACTION_ID, mMiscellaneousViewTransactionId); 214 } 215 216 @Override 217 public void onWindowFocusChanged(boolean hasFocus) { 218 if (hasFocus && !mLayout.isConversationListCollapsed()) { 219 // The conversation list is visible. 220 informCursorVisiblity(true); 221 } 222 } 223 224 @Override 225 public void switchToDefaultInboxOrChangeAccount(Account account) { 226 if (mViewMode.isSearchMode()) { 227 // We are in an activity on top of the main navigation activity. 228 // We need to return to it with a result code that indicates it should navigate to 229 // a different folder. 230 final Intent intent = new Intent(); 231 intent.putExtra(AbstractActivityController.EXTRA_ACCOUNT, account); 232 mActivity.setResult(Activity.RESULT_OK, intent); 233 mActivity.finish(); 234 return; 235 } 236 if (mViewMode.getMode() != ViewMode.CONVERSATION_LIST) { 237 mViewMode.enterConversationListMode(); 238 } 239 super.switchToDefaultInboxOrChangeAccount(account); 240 } 241 242 @Override 243 public void onFolderSelected(Folder folder) { 244 // It's possible that we are not in conversation list mode 245 if (mViewMode.isSearchMode()) { 246 // We are in an activity on top of the main navigation activity. 247 // We need to return to it with a result code that indicates it should navigate to 248 // a different folder. 249 final Intent intent = new Intent(); 250 intent.putExtra(AbstractActivityController.EXTRA_FOLDER, folder); 251 mActivity.setResult(Activity.RESULT_OK, intent); 252 mActivity.finish(); 253 return; 254 } else if (mViewMode.getMode() != ViewMode.CONVERSATION_LIST) { 255 mViewMode.enterConversationListMode(); 256 } 257 258 setHierarchyFolder(folder); 259 super.onFolderSelected(folder); 260 } 261 262 public boolean isDrawerOpen() { 263 final FolderListFragment flf = getFolderListFragment(); 264 return flf != null && !flf.isMinimized(); 265 } 266 267 @Override 268 protected void toggleDrawerState() { 269 final FolderListFragment flf = getFolderListFragment(); 270 if (flf == null) { 271 LogUtils.w(LOG_TAG, "no drawer to toggle open/closed"); 272 return; 273 } 274 flf.setMinimized(!flf.isMinimized()); 275 mLayout.requestLayout(); 276 resetActionBarIcon(); 277 278 final ConversationListFragment clf = getConversationListFragment(); 279 if (clf != null) { 280 clf.setNextFocusStartId(getClfNextFocusStartId()); 281 282 final SwipeableListView list = clf.getListView(); 283 if (list != null) { 284 if (flf.isMinimized()) { 285 list.stopPreventingSwipes(); 286 } else { 287 list.preventSwipesEntirely(); 288 } 289 } 290 } 291 } 292 293 @Override 294 public boolean shouldPreventListSwipesEntirely() { 295 return isDrawerOpen(); 296 } 297 298 @Override 299 public void onViewModeChanged(int newMode) { 300 if (!mSavedMiscellaneousView && mMiscellaneousViewTransactionId >= 0) { 301 final FragmentManager fragmentManager = mActivity.getFragmentManager(); 302 fragmentManager.popBackStackImmediate(mMiscellaneousViewTransactionId, 303 FragmentManager.POP_BACK_STACK_INCLUSIVE); 304 mMiscellaneousViewTransactionId = -1; 305 } 306 mSavedMiscellaneousView = false; 307 308 super.onViewModeChanged(newMode); 309 if (newMode != ViewMode.WAITING_FOR_ACCOUNT_INITIALIZATION) { 310 // Clear the wait fragment 311 hideWaitForInitialization(); 312 } 313 // In conversation mode, if the conversation list is not visible, then the user cannot 314 // see the selected conversations. Disable the CAB mode while leaving the selected set 315 // untouched. 316 // When the conversation list is made visible again, try to enable the CAB 317 // mode if any conversations are selected. 318 if (newMode == ViewMode.CONVERSATION || newMode == ViewMode.CONVERSATION_LIST 319 || ViewMode.isAdMode(newMode)) { 320 enableOrDisableCab(); 321 } 322 } 323 324 private @IdRes int getClfNextFocusStartId() { 325 return (isDrawerOpen()) ? android.R.id.list : R.id.mini_drawer; 326 } 327 328 @Override 329 public void onConversationVisibilityChanged(boolean visible) { 330 super.onConversationVisibilityChanged(visible); 331 if (!visible) { 332 mPagerController.hide(false /* changeVisibility */); 333 } else if (mConversationToShow != null) { 334 if (mCurrentConversationJustPeeking) { 335 mHandler.removeCallbacks(mPeekConversationRunnable); 336 mHandler.postDelayed(mPeekConversationRunnable, PEEK_CONVERSATION_DELAY_MS); 337 } else { 338 showCurrentConversationInPager(); 339 } 340 } 341 342 // Change visibility of the empty view 343 if (mIsTabletLandscape) { 344 mEmptyCvView.setVisibility(visible ? View.GONE : View.VISIBLE); 345 } 346 } 347 348 private void showCurrentConversationInPager() { 349 if (mConversationToShow != null) { 350 mPagerController.show(mAccount, mFolder, mConversationToShow, 351 false /* changeVisibility */); 352 mConversationToShow = null; 353 } 354 } 355 356 @Override 357 public void onConversationListVisibilityChanged(boolean visible) { 358 super.onConversationListVisibilityChanged(visible); 359 enableOrDisableCab(); 360 } 361 362 @Override 363 public void resetActionBarIcon() { 364 final ActionBar ab = mActivity.getSupportActionBar(); 365 final boolean isChildFolder = getFolder() != null && !Utils.isEmpty(getFolder().parent); 366 if (isHidingConversationList() || isChildFolder) { 367 ab.setHomeAsUpIndicator(R.drawable.ic_arrow_back_wht_24dp_with_rtl); 368 ab.setHomeActionContentDescription(0 /* system default */); 369 } else { 370 ab.setHomeAsUpIndicator(R.drawable.ic_drawer); 371 ab.setHomeActionContentDescription( 372 isDrawerOpen() ? R.string.drawer_close : R.string.drawer_open); 373 } 374 } 375 376 /** 377 * Enable or disable the CAB mode based on the visibility of the conversation list fragment. 378 */ 379 private void enableOrDisableCab() { 380 if (mLayout.isConversationListCollapsed()) { 381 disableCabMode(); 382 } else { 383 enableCabMode(); 384 } 385 } 386 387 @Override 388 public void onSetPopulated(ConversationCheckedSet set) { 389 super.onSetPopulated(set); 390 391 boolean showSenderImage = 392 (mAccount.settings.convListIcon == ConversationListIcon.SENDER_IMAGE); 393 if (!showSenderImage && mViewMode.isListMode()) { 394 getConversationListFragment().setChoiceNone(); 395 } 396 } 397 398 @Override 399 public void onSetEmpty() { 400 super.onSetEmpty(); 401 402 boolean showSenderImage = 403 (mAccount.settings.convListIcon == ConversationListIcon.SENDER_IMAGE); 404 if (!showSenderImage && mViewMode.isListMode()) { 405 getConversationListFragment().revertChoiceMode(); 406 } 407 } 408 409 @Override 410 protected void showConversation(Conversation conversation, boolean peek) { 411 // Make sure that we set the peeking flag before calling super (since some functionality 412 // in super depends on the flag. 413 mCurrentConversationJustPeeking = peek; 414 super.showConversation(conversation, peek); 415 416 // 2-pane can ignore inLoaderCallbacks because it doesn't use 417 // FragmentManager.popBackStack(). 418 419 if (mActivity == null) { 420 return; 421 } 422 if (conversation == null) { 423 handleBackPress(); 424 return; 425 } 426 // If conversation list is not visible, then the user cannot see the CAB mode, so exit it. 427 // This is needed here (in addition to during viewmode changes) because orientation changes 428 // while viewing a conversation don't change the viewmode: the mode stays 429 // ViewMode.CONVERSATION and yet the conversation list goes in and out of visibility. 430 enableOrDisableCab(); 431 432 // close the drawer, if open 433 if (isDrawerOpen()) { 434 toggleDrawerState(); 435 } 436 437 // When a mode change is required, wait for onConversationVisibilityChanged(), the signal 438 // that the mode change animation has finished, before rendering the conversation. 439 mConversationToShow = conversation; 440 441 final int mode = mViewMode.getMode(); 442 LogUtils.i(LOG_TAG, "IN TPC.showConv, oldMode=%s conv=%s", mode, mConversationToShow); 443 if (mode == ViewMode.SEARCH_RESULTS_LIST || mode == ViewMode.SEARCH_RESULTS_CONVERSATION) { 444 mViewMode.enterSearchResultsConversationMode(); 445 } else { 446 mViewMode.enterConversationMode(); 447 } 448 // load the conversation immediately if we're already in conversation mode 449 if (!mLayout.isModeChangePending()) { 450 onConversationVisibilityChanged(true); 451 } else { 452 LogUtils.i(LOG_TAG, "TPC.showConversation will wait for TPL.animationEnd to show!"); 453 } 454 } 455 456 @Override 457 public void onConversationSelected(Conversation conversation, boolean inLoaderCallbacks) { 458 super.onConversationSelected(conversation, inLoaderCallbacks); 459 if (!mCurrentConversationJustPeeking) { 460 // Shift the focus to the conversation in landscape mode. 461 mPagerController.focusPager(); 462 } 463 } 464 465 @Override 466 public void onConversationFocused(Conversation conversation) { 467 if (mIsTabletLandscape) { 468 showConversation(conversation, true /* peek */); 469 } 470 } 471 472 @Override 473 public void setCurrentConversation(Conversation conversation) { 474 // Order is important! We want to calculate different *before* the superclass changes 475 // mCurrentConversation, so before super.setCurrentConversation(). 476 final long oldId = mCurrentConversation != null ? mCurrentConversation.id : -1; 477 final long newId = conversation != null ? conversation.id : -1; 478 final boolean different = oldId != newId; 479 480 // This call might change mCurrentConversation. 481 super.setCurrentConversation(conversation); 482 483 final ConversationListFragment convList = getConversationListFragment(); 484 if (convList != null && conversation != null) { 485 if (mCurrentConversationJustPeeking) { 486 convList.clearChoicesAndActivated(); 487 } else { 488 convList.setActivated(conversation.position, different); 489 } 490 } 491 } 492 493 @Override 494 protected void showWaitForInitialization() { 495 super.showWaitForInitialization(); 496 497 FragmentTransaction fragmentTransaction = mActivity.getFragmentManager().beginTransaction(); 498 fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN); 499 fragmentTransaction.replace(R.id.conversation_list_place_holder, getWaitFragment(), TAG_WAIT); 500 fragmentTransaction.commitAllowingStateLoss(); 501 } 502 503 @Override 504 protected void hideWaitForInitialization() { 505 final WaitFragment waitFragment = getWaitFragment(); 506 if (waitFragment == null) { 507 // We aren't showing a wait fragment: nothing to do 508 return; 509 } 510 // Remove the existing wait fragment from the back stack. 511 final FragmentTransaction fragmentTransaction = 512 mActivity.getFragmentManager().beginTransaction(); 513 fragmentTransaction.remove(waitFragment); 514 fragmentTransaction.commitAllowingStateLoss(); 515 super.hideWaitForInitialization(); 516 if (mViewMode.isWaitingForSync()) { 517 // We should come out of wait mode and display the account inbox. 518 loadAccountInbox(); 519 } 520 } 521 522 /** 523 * Up works as follows: 524 * 1) If the user is in a conversation and: 525 * a) the conversation list is hidden (portrait mode), shows the conv list and 526 * stays in conversation view mode. 527 * b) the conversation list is shown, goes back to conversation list mode. 528 * 2) If the user is in search results, up exits search. 529 * mode and returns the user to whatever view they were in when they began search. 530 * 3) If the user is in conversation list mode, there is no up. 531 */ 532 @Override 533 public boolean handleUpPress() { 534 if (isHidingConversationList()) { 535 handleBackPress(); 536 } else { 537 toggleDrawerState(); 538 } 539 540 return true; 541 } 542 543 @Override 544 public boolean handleBackPress() { 545 // Clear any visible undo bars. 546 mToastBar.hide(false, false /* actionClicked */); 547 if (isDrawerOpen()) { 548 toggleDrawerState(); 549 } else { 550 popView(false); 551 } 552 return true; 553 } 554 555 /** 556 * Pops the "view stack" to the last screen the user was viewing. 557 * 558 * @param preventClose Whether to prevent closing the app if the stack is empty. 559 */ 560 protected void popView(boolean preventClose) { 561 // If the user is in search query entry mode, or the user is viewing 562 // search results, exit 563 // the mode. 564 int mode = mViewMode.getMode(); 565 if (mode == ViewMode.SEARCH_RESULTS_LIST) { 566 mActivity.finish(); 567 } else if (mode == ViewMode.CONVERSATION || mViewMode.isAdMode()) { 568 // Go to conversation list. 569 mViewMode.enterConversationListMode(); 570 } else if (mode == ViewMode.SEARCH_RESULTS_CONVERSATION) { 571 mViewMode.enterSearchResultsListMode(); 572 } else { 573 // The Folder List fragment can be null for monkeys where we get a back before the 574 // folder list has had a chance to initialize. 575 final FolderListFragment folderList = getFolderListFragment(); 576 if (mode == ViewMode.CONVERSATION_LIST && folderList != null 577 && !Folder.isRoot(mFolder)) { 578 // If the user navigated via the left folders list into a child folder, 579 // back should take the user up to the parent folder's conversation list. 580 navigateUpFolderHierarchy(); 581 // Otherwise, if we are in the conversation list but not in the default 582 // inbox and not on expansive layouts, we want to switch back to the default 583 // inbox. This fixes b/9006969 so that on smaller tablets where we have this 584 // hybrid one and two-pane mode, we will return to the inbox. On larger tablets, 585 // we will instead exit the app. 586 } else if (!preventClose) { 587 // There is nothing else to pop off the stack. 588 mActivity.finish(); 589 } 590 } 591 } 592 593 @Override 594 public boolean shouldShowFirstConversation() { 595 return Intent.ACTION_SEARCH.equals(mActivity.getIntent().getAction()) 596 && shouldEnterSearchConvMode(); 597 } 598 599 @Override 600 public void onUndoAvailable(ToastBarOperation op) { 601 final int mode = mViewMode.getMode(); 602 final ConversationListFragment convList = getConversationListFragment(); 603 604 switch (mode) { 605 case ViewMode.SEARCH_RESULTS_LIST: 606 case ViewMode.CONVERSATION_LIST: 607 case ViewMode.SEARCH_RESULTS_CONVERSATION: 608 case ViewMode.CONVERSATION: 609 if (convList != null) { 610 mToastBar.show(getUndoClickedListener(convList.getAnimatedAdapter()), 611 Utils.convertHtmlToPlainText 612 (op.getDescription(mActivity.getActivityContext())), 613 R.string.undo, 614 true /* replaceVisibleToast */, 615 true /* autohide */, 616 op); 617 } 618 } 619 } 620 621 @Override 622 public void onError(final Folder folder, boolean replaceVisibleToast) { 623 showErrorToast(folder, replaceVisibleToast); 624 } 625 626 @Override 627 public boolean isDrawerEnabled() { 628 // two-pane has its own drawer-like thing that expands inline from a minimized state. 629 return false; 630 } 631 632 @Override 633 public int getFolderListViewChoiceMode() { 634 // By default, we want to allow one item to be selected in the folder list 635 return ListView.CHOICE_MODE_SINGLE; 636 } 637 638 private int mMiscellaneousViewTransactionId = -1; 639 640 @Override 641 public void launchFragment(final Fragment fragment, final int selectPosition) { 642 final int containerViewId = TwoPaneLayout.MISCELLANEOUS_VIEW_ID; 643 644 final FragmentManager fragmentManager = mActivity.getFragmentManager(); 645 if (fragmentManager.findFragmentByTag(TAG_CUSTOM_FRAGMENT) == null) { 646 final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); 647 fragmentTransaction.addToBackStack(null); 648 fragmentTransaction.replace(containerViewId, fragment, TAG_CUSTOM_FRAGMENT); 649 mMiscellaneousViewTransactionId = fragmentTransaction.commitAllowingStateLoss(); 650 fragmentManager.executePendingTransactions(); 651 } 652 653 if (selectPosition >= 0) { 654 getConversationListFragment().setRawActivated(selectPosition, true); 655 } 656 } 657 658 @Override 659 public boolean onInterceptCVDownEvent() { 660 // handle a down event on CV by closing the drawer if open 661 if (isDrawerOpen()) { 662 toggleDrawerState(); 663 return true; 664 } 665 return false; 666 } 667 668 @Override 669 public boolean onInterceptKeyFromCV(int keyCode, KeyEvent keyEvent, boolean navigateAway) { 670 // Override left/right key presses in landscape mode. 671 if (navigateAway) { 672 if (keyEvent.getAction() == KeyEvent.ACTION_UP) { 673 ConversationListFragment clf = getConversationListFragment(); 674 if (clf != null) { 675 clf.getListView().requestFocus(); 676 } 677 } 678 return true; 679 } 680 return false; 681 } 682 683 @Override 684 public boolean isTwoPaneLandscape() { 685 return mIsTabletLandscape; 686 } 687 688 @Override 689 public boolean shouldShowSearchBarByDefault() { 690 final int mode = mViewMode.getMode(); 691 return mode == ViewMode.SEARCH_RESULTS_LIST || 692 (mIsTabletLandscape && mode == ViewMode.SEARCH_RESULTS_CONVERSATION); 693 } 694 695 @Override 696 public boolean shouldShowSearchMenuItem() { 697 final int mode = mViewMode.getMode(); 698 return mode == ViewMode.CONVERSATION_LIST || 699 (mIsTabletLandscape && mode == ViewMode.CONVERSATION); 700 } 701 702 @Override 703 public void addConversationListLayoutListener( 704 TwoPaneLayout.ConversationListLayoutListener listener) { 705 mConversationListLayoutListeners.add(listener); 706 } 707 708 public List<TwoPaneLayout.ConversationListLayoutListener> getConversationListLayoutListeners() { 709 return mConversationListLayoutListeners; 710 } 711 712 @Override 713 public boolean setupEmptyIconView(Folder folder, boolean isEmpty) { 714 if (mIsTabletLandscape) { 715 if (!isEmpty) { 716 mEmptyCvView.setImageResource(R.drawable.ic_empty_cv_120dp); 717 } else { 718 EmptyStateUtils.bindEmptyFolderIcon(mEmptyCvView, folder); 719 } 720 return true; 721 } 722 return false; 723 } 724} 725