TwoPaneController.java revision 5fa4018360ea05c2d9cdc7cfd7abd5f74bc83002
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
275        setDrawerState(!flf.isMinimized());
276    }
277
278    protected void setDrawerState(boolean minimized) {
279        final FolderListFragment flf = getFolderListFragment();
280        if (flf == null) {
281            LogUtils.w(LOG_TAG, "no drawer to toggle open/closed");
282            return;
283        }
284
285        flf.setMinimized(minimized);
286        mLayout.animateDrawer(minimized);
287        resetActionBarIcon();
288
289        final ConversationListFragment clf = getConversationListFragment();
290        if (clf != null) {
291            clf.setNextFocusStartId(getClfNextFocusStartId());
292
293            final SwipeableListView list = clf.getListView();
294            if (list != null) {
295                if (minimized) {
296                    list.stopPreventingSwipes();
297                } else {
298                    list.preventSwipesEntirely();
299                }
300            }
301        }
302    }
303
304    @Override
305    public boolean shouldPreventListSwipesEntirely() {
306        return isDrawerOpen();
307    }
308
309    @Override
310    public void onViewModeChanged(int newMode) {
311        if (!mSavedMiscellaneousView && mMiscellaneousViewTransactionId >= 0) {
312            final FragmentManager fragmentManager = mActivity.getFragmentManager();
313            fragmentManager.popBackStackImmediate(mMiscellaneousViewTransactionId,
314                    FragmentManager.POP_BACK_STACK_INCLUSIVE);
315            mMiscellaneousViewTransactionId = -1;
316        }
317        mSavedMiscellaneousView = false;
318
319        super.onViewModeChanged(newMode);
320        if (newMode != ViewMode.WAITING_FOR_ACCOUNT_INITIALIZATION) {
321            // Clear the wait fragment
322            hideWaitForInitialization();
323        }
324        // In conversation mode, if the conversation list is not visible, then the user cannot
325        // see the selected conversations. Disable the CAB mode while leaving the selected set
326        // untouched.
327        // When the conversation list is made visible again, try to enable the CAB
328        // mode if any conversations are selected.
329        if (newMode == ViewMode.CONVERSATION || newMode == ViewMode.CONVERSATION_LIST
330                || ViewMode.isAdMode(newMode)) {
331            enableOrDisableCab();
332        }
333    }
334
335    private @IdRes int getClfNextFocusStartId() {
336        return (isDrawerOpen()) ? android.R.id.list : R.id.mini_drawer;
337    }
338
339    @Override
340    public void onConversationVisibilityChanged(boolean visible) {
341        super.onConversationVisibilityChanged(visible);
342        if (!visible) {
343            mPagerController.hide(false /* changeVisibility */);
344        } else if (mConversationToShow != null) {
345            if (mCurrentConversationJustPeeking) {
346                mHandler.removeCallbacks(mPeekConversationRunnable);
347                mHandler.postDelayed(mPeekConversationRunnable, PEEK_CONVERSATION_DELAY_MS);
348            } else {
349                showCurrentConversationInPager();
350            }
351        }
352
353        // Change visibility of the empty view
354        if (mIsTabletLandscape) {
355            mEmptyCvView.setVisibility(visible ? View.GONE : View.VISIBLE);
356        }
357    }
358
359    private void showCurrentConversationInPager() {
360        if (mConversationToShow != null) {
361            mPagerController.show(mAccount, mFolder, mConversationToShow,
362                    false /* changeVisibility */, null /* pagerAnimationListener */);
363            mConversationToShow = null;
364        }
365    }
366
367    @Override
368    public void onConversationListVisibilityChanged(boolean visible) {
369        super.onConversationListVisibilityChanged(visible);
370        enableOrDisableCab();
371    }
372
373    @Override
374    public void resetActionBarIcon() {
375        final ActionBar ab = mActivity.getSupportActionBar();
376        final boolean isChildFolder = getFolder() != null && !Utils.isEmpty(getFolder().parent);
377        if (isHidingConversationList() || isChildFolder) {
378            ab.setHomeAsUpIndicator(R.drawable.ic_arrow_back_wht_24dp_with_rtl);
379            ab.setHomeActionContentDescription(0 /* system default */);
380        } else {
381            ab.setHomeAsUpIndicator(R.drawable.ic_menu_wht_24dp);
382            ab.setHomeActionContentDescription(
383                    isDrawerOpen() ? R.string.drawer_close : R.string.drawer_open);
384        }
385    }
386
387    /**
388     * Enable or disable the CAB mode based on the visibility of the conversation list fragment.
389     */
390    private void enableOrDisableCab() {
391        if (mLayout.isConversationListCollapsed()) {
392            disableCabMode();
393        } else {
394            enableCabMode();
395        }
396    }
397
398    @Override
399    public void onSetPopulated(ConversationCheckedSet set) {
400        super.onSetPopulated(set);
401
402        boolean showSenderImage =
403                (mAccount.settings.convListIcon == ConversationListIcon.SENDER_IMAGE);
404        if (!showSenderImage && mViewMode.isListMode()) {
405            getConversationListFragment().setChoiceNone();
406        }
407    }
408
409    @Override
410    public void onSetEmpty() {
411        super.onSetEmpty();
412
413        boolean showSenderImage =
414                (mAccount.settings.convListIcon == ConversationListIcon.SENDER_IMAGE);
415        if (!showSenderImage && mViewMode.isListMode()) {
416            getConversationListFragment().revertChoiceMode();
417        }
418    }
419
420    @Override
421    protected void showConversationWithPeek(Conversation conversation, boolean peek) {
422        // Make sure that we set the peeking flag before calling super (since some functionality
423        // in super depends on the flag.
424        mCurrentConversationJustPeeking = peek;
425        super.showConversationWithPeek(conversation, peek);
426
427        // 2-pane can ignore inLoaderCallbacks because it doesn't use
428        // FragmentManager.popBackStack().
429
430        if (mActivity == null) {
431            return;
432        }
433        if (conversation == null) {
434            handleBackPress();
435            return;
436        }
437        // If conversation list is not visible, then the user cannot see the CAB mode, so exit it.
438        // This is needed here (in addition to during viewmode changes) because orientation changes
439        // while viewing a conversation don't change the viewmode: the mode stays
440        // ViewMode.CONVERSATION and yet the conversation list goes in and out of visibility.
441        enableOrDisableCab();
442
443        // close the drawer, if open
444        if (isDrawerOpen()) {
445            toggleDrawerState();
446        }
447
448        // When a mode change is required, wait for onConversationVisibilityChanged(), the signal
449        // that the mode change animation has finished, before rendering the conversation.
450        mConversationToShow = conversation;
451
452        final int mode = mViewMode.getMode();
453        LogUtils.i(LOG_TAG, "IN TPC.showConv, oldMode=%s conv=%s", mode, mConversationToShow);
454        if (mode == ViewMode.SEARCH_RESULTS_LIST || mode == ViewMode.SEARCH_RESULTS_CONVERSATION) {
455            mViewMode.enterSearchResultsConversationMode();
456        } else {
457            mViewMode.enterConversationMode();
458        }
459        // load the conversation immediately if we're already in conversation mode
460        if (!mLayout.isModeChangePending()) {
461            onConversationVisibilityChanged(true);
462        } else {
463            LogUtils.i(LOG_TAG, "TPC.showConversation will wait for TPL.animationEnd to show!");
464        }
465    }
466
467    @Override
468    public void onConversationSelected(Conversation conversation, boolean inLoaderCallbacks) {
469        super.onConversationSelected(conversation, inLoaderCallbacks);
470        if (!mCurrentConversationJustPeeking) {
471            // Shift the focus to the conversation in landscape mode.
472            mPagerController.focusPager();
473        }
474    }
475
476    @Override
477    public void onConversationFocused(Conversation conversation) {
478        if (mIsTabletLandscape) {
479            showConversationWithPeek(conversation, true /* peek */);
480        }
481    }
482
483    @Override
484    public void setCurrentConversation(Conversation conversation) {
485        // Order is important! We want to calculate different *before* the superclass changes
486        // mCurrentConversation, so before super.setCurrentConversation().
487        final long oldId = mCurrentConversation != null ? mCurrentConversation.id : -1;
488        final long newId = conversation != null ? conversation.id : -1;
489        final boolean different = oldId != newId;
490
491        // This call might change mCurrentConversation.
492        super.setCurrentConversation(conversation);
493
494        final ConversationListFragment convList = getConversationListFragment();
495        if (convList != null && conversation != null) {
496            if (mCurrentConversationJustPeeking) {
497                convList.clearChoicesAndActivated();
498            } else {
499                convList.setActivated(conversation.position, different);
500            }
501        }
502    }
503
504    @Override
505    protected void showWaitForInitialization() {
506        super.showWaitForInitialization();
507
508        FragmentTransaction fragmentTransaction = mActivity.getFragmentManager().beginTransaction();
509        fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
510        fragmentTransaction.replace(R.id.conversation_list_place_holder, getWaitFragment(), TAG_WAIT);
511        fragmentTransaction.commitAllowingStateLoss();
512    }
513
514    @Override
515    protected void hideWaitForInitialization() {
516        final WaitFragment waitFragment = getWaitFragment();
517        if (waitFragment == null) {
518            // We aren't showing a wait fragment: nothing to do
519            return;
520        }
521        // Remove the existing wait fragment from the back stack.
522        final FragmentTransaction fragmentTransaction =
523                mActivity.getFragmentManager().beginTransaction();
524        fragmentTransaction.remove(waitFragment);
525        fragmentTransaction.commitAllowingStateLoss();
526        super.hideWaitForInitialization();
527        if (mViewMode.isWaitingForSync()) {
528            // We should come out of wait mode and display the account inbox.
529            loadAccountInbox();
530        }
531    }
532
533    /**
534     * Up works as follows:
535     * 1) If the user is in a conversation and:
536     *  a) the conversation list is hidden (portrait mode), shows the conv list and
537     *  stays in conversation view mode.
538     *  b) the conversation list is shown, goes back to conversation list mode.
539     * 2) If the user is in search results, up exits search.
540     * mode and returns the user to whatever view they were in when they began search.
541     * 3) If the user is in conversation list mode, there is no up.
542     */
543    @Override
544    public boolean handleUpPress() {
545        if (isHidingConversationList()) {
546            handleBackPress();
547        } else {
548            toggleDrawerState();
549        }
550
551        return true;
552    }
553
554    @Override
555    public boolean handleBackPress() {
556        // Clear any visible undo bars.
557        mToastBar.hide(false, false /* actionClicked */);
558        if (isDrawerOpen()) {
559            toggleDrawerState();
560        } else {
561            popView(false);
562        }
563        return true;
564    }
565
566    /**
567     * Pops the "view stack" to the last screen the user was viewing.
568     *
569     * @param preventClose Whether to prevent closing the app if the stack is empty.
570     */
571    protected void popView(boolean preventClose) {
572        // If the user is in search query entry mode, or the user is viewing
573        // search results, exit
574        // the mode.
575        int mode = mViewMode.getMode();
576        if (mode == ViewMode.SEARCH_RESULTS_LIST) {
577            mActivity.finish();
578        } else if (mode == ViewMode.CONVERSATION || mViewMode.isAdMode()) {
579            // Go to conversation list.
580            mViewMode.enterConversationListMode();
581        } else if (mode == ViewMode.SEARCH_RESULTS_CONVERSATION) {
582            mViewMode.enterSearchResultsListMode();
583        } else {
584            // The Folder List fragment can be null for monkeys where we get a back before the
585            // folder list has had a chance to initialize.
586            final FolderListFragment folderList = getFolderListFragment();
587            if (mode == ViewMode.CONVERSATION_LIST && folderList != null
588                    && !Folder.isRoot(mFolder)) {
589                // If the user navigated via the left folders list into a child folder,
590                // back should take the user up to the parent folder's conversation list.
591                navigateUpFolderHierarchy();
592            // Otherwise, if we are in the conversation list but not in the default
593            // inbox and not on expansive layouts, we want to switch back to the default
594            // inbox. This fixes b/9006969 so that on smaller tablets where we have this
595            // hybrid one and two-pane mode, we will return to the inbox. On larger tablets,
596            // we will instead exit the app.
597            } else if (!preventClose) {
598                // There is nothing else to pop off the stack.
599                mActivity.finish();
600            }
601        }
602    }
603
604    @Override
605    public boolean shouldShowFirstConversation() {
606        return Intent.ACTION_SEARCH.equals(mActivity.getIntent().getAction())
607                && shouldEnterSearchConvMode();
608    }
609
610    @Override
611    public void onUndoAvailable(ToastBarOperation op) {
612        final int mode = mViewMode.getMode();
613        final ConversationListFragment convList = getConversationListFragment();
614
615        switch (mode) {
616            case ViewMode.SEARCH_RESULTS_LIST:
617            case ViewMode.CONVERSATION_LIST:
618            case ViewMode.SEARCH_RESULTS_CONVERSATION:
619            case ViewMode.CONVERSATION:
620                if (convList != null) {
621                    mToastBar.show(getUndoClickedListener(convList.getAnimatedAdapter()),
622                            Utils.convertHtmlToPlainText
623                                (op.getDescription(mActivity.getActivityContext())),
624                            R.string.undo,
625                            true /* replaceVisibleToast */,
626                            true /* autohide */,
627                            op);
628                }
629        }
630    }
631
632    @Override
633    public void onError(final Folder folder, boolean replaceVisibleToast) {
634        showErrorToast(folder, replaceVisibleToast);
635    }
636
637    @Override
638    public boolean isDrawerEnabled() {
639        // two-pane has its own drawer-like thing that expands inline from a minimized state.
640        return false;
641    }
642
643    @Override
644    public int getFolderListViewChoiceMode() {
645        // By default, we want to allow one item to be selected in the folder list
646        return ListView.CHOICE_MODE_SINGLE;
647    }
648
649    private int mMiscellaneousViewTransactionId = -1;
650
651    @Override
652    public void launchFragment(final Fragment fragment, final int selectPosition) {
653        final int containerViewId = TwoPaneLayout.MISCELLANEOUS_VIEW_ID;
654
655        final FragmentManager fragmentManager = mActivity.getFragmentManager();
656        if (fragmentManager.findFragmentByTag(TAG_CUSTOM_FRAGMENT) == null) {
657            final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
658            fragmentTransaction.addToBackStack(null);
659            fragmentTransaction.replace(containerViewId, fragment, TAG_CUSTOM_FRAGMENT);
660            mMiscellaneousViewTransactionId = fragmentTransaction.commitAllowingStateLoss();
661            fragmentManager.executePendingTransactions();
662        }
663
664        if (selectPosition >= 0) {
665            getConversationListFragment().setRawActivated(selectPosition, true);
666        }
667    }
668
669    @Override
670    public boolean onInterceptCVDownEvent() {
671        // handle a down event on CV by closing the drawer if open
672        if (isDrawerOpen()) {
673            toggleDrawerState();
674            return true;
675        }
676        return false;
677    }
678
679    @Override
680    public boolean onInterceptKeyFromCV(int keyCode, KeyEvent keyEvent, boolean navigateAway) {
681        // Override left/right key presses in landscape mode.
682        if (navigateAway) {
683            if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
684                ConversationListFragment clf = getConversationListFragment();
685                if (clf != null) {
686                    clf.getListView().requestFocus();
687                }
688            }
689            return true;
690        }
691        return false;
692    }
693
694    @Override
695    public boolean isTwoPaneLandscape() {
696        return mIsTabletLandscape;
697    }
698
699    @Override
700    public boolean shouldShowSearchBarByDefault() {
701        final int mode = mViewMode.getMode();
702        return mode == ViewMode.SEARCH_RESULTS_LIST ||
703                (mIsTabletLandscape && mode == ViewMode.SEARCH_RESULTS_CONVERSATION);
704    }
705
706    @Override
707    public boolean shouldShowSearchMenuItem() {
708        final int mode = mViewMode.getMode();
709        return mode == ViewMode.CONVERSATION_LIST ||
710                (mIsTabletLandscape && mode == ViewMode.CONVERSATION);
711    }
712
713    @Override
714    public void addConversationListLayoutListener(
715            TwoPaneLayout.ConversationListLayoutListener listener) {
716        mConversationListLayoutListeners.add(listener);
717    }
718
719    public List<TwoPaneLayout.ConversationListLayoutListener> getConversationListLayoutListeners() {
720        return mConversationListLayoutListeners;
721    }
722
723    @Override
724    public boolean setupEmptyIconView(Folder folder, boolean isEmpty) {
725        if (mIsTabletLandscape) {
726            if (!isEmpty) {
727                mEmptyCvView.setImageResource(R.drawable.ic_empty_default);
728            } else {
729                EmptyStateUtils.bindEmptyFolderIcon(mEmptyCvView, folder);
730            }
731            return true;
732        }
733        return false;
734    }
735}
736