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