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