UIControllerTwoPane.java revision 570a2f75be21af378601abc0ae829bfaa71c2674
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.email.activity;
18
19import android.app.Activity;
20import android.app.FragmentTransaction;
21import android.content.Context;
22import android.os.Bundle;
23import android.util.Log;
24import android.view.Menu;
25import android.view.MenuInflater;
26
27import com.android.email.Clock;
28import com.android.email.Email;
29import com.android.email.MessageListContext;
30import com.android.email.Preferences;
31import com.android.email.R;
32import com.android.email.RefreshManager;
33import com.android.emailcommon.Logging;
34import com.android.emailcommon.provider.Account;
35import com.android.emailcommon.provider.EmailContent.Message;
36import com.android.emailcommon.provider.Mailbox;
37import com.android.emailcommon.utility.EmailAsyncTask;
38import com.android.emailcommon.utility.Utility;
39import com.google.common.annotations.VisibleForTesting;
40
41import java.util.Set;
42
43/**
44 * UI Controller for x-large devices.  Supports a multi-pane layout.
45 *
46 * Note: Always use {@link #commitFragmentTransaction} to operate fragment transactions,
47 * so that we can easily switch between synchronous and asynchronous transactions.
48 */
49class UIControllerTwoPane extends UIControllerBase implements ThreePaneLayout.Callback {
50    @VisibleForTesting
51    static final int MAILBOX_REFRESH_MIN_INTERVAL = 30 * 1000; // in milliseconds
52
53    @VisibleForTesting
54    static final int INBOX_AUTO_REFRESH_MIN_INTERVAL = 10 * 1000; // in milliseconds
55
56    // Other UI elements
57    protected ThreePaneLayout mThreePane;
58
59    private MessageCommandButtonView mMessageCommandButtons;
60
61    private MessageCommandButtonView mInMessageCommandButtons;
62
63    public UIControllerTwoPane(EmailActivity activity) {
64        super(activity);
65    }
66
67    @Override
68    public int getLayoutId() {
69        return R.layout.email_activity_two_pane;
70    }
71
72    // ThreePaneLayoutCallback
73    @Override
74    public void onVisiblePanesChanged(int previousVisiblePanes) {
75        // If the right pane is gone, remove the message view.
76        final int visiblePanes = mThreePane.getVisiblePanes();
77
78        if (((visiblePanes & ThreePaneLayout.PANE_RIGHT) == 0) &&
79                ((previousVisiblePanes & ThreePaneLayout.PANE_RIGHT) != 0)) {
80            // Message view just got hidden
81            unselectMessage();
82        }
83        // Disable CAB when the message list is not visible.
84        if (isMessageListInstalled()) {
85            getMessageListFragment().onHidden((visiblePanes & ThreePaneLayout.PANE_MIDDLE) == 0);
86        }
87        refreshActionBar();
88    }
89
90    // MailboxListFragment$Callback
91    @Override
92    public void onMailboxSelected(long accountId, long mailboxId, boolean nestedNavigation) {
93        setListContext(MessageListContext.forMailbox(accountId, mailboxId));
94        if (getMessageListMailboxId() != mListContext.getMailboxId()) {
95            updateMessageList(true);
96        }
97    }
98
99    /**
100     * Handles the {@link android.app.Activity#onCreateOptionsMenu} callback.
101     */
102    public boolean onCreateOptionsMenu(MenuInflater inflater, Menu menu) {
103        int state = mThreePane.getPaneState();
104        boolean handled = false;
105        int menuId = -1;
106        switch (state) {
107            case ThreePaneLayout.STATE_LEFT_VISIBLE:
108                if (this.getMessageListFragment().getListContext().isSearch()) {
109                    menuId = R.menu.message_search_list_fragment_option;
110                } else {
111                    menuId = R.menu.message_list_fragment_option;
112                }
113                handled=  true;
114                break;
115            case ThreePaneLayout.STATE_MIDDLE_EXPANDED:
116            case ThreePaneLayout.STATE_RIGHT_VISIBLE:
117                menuId = R.menu.message_view_fragment_option;
118                handled=  true;
119                break;
120        }
121        if (menuId != -1) {
122            inflater.inflate(menuId, menu);
123        }
124        return handled;
125    }
126
127    // MailboxListFragment$Callback
128    @Override
129    public void onAccountSelected(long accountId) {
130        // It's from combined view, so "forceShowInbox" doesn't really matter.
131        // (We're always switching accounts.)
132        switchAccount(accountId, true);
133    }
134
135    // MailboxListFragment$Callback
136    @Override
137    public void onParentMailboxChanged() {
138        refreshActionBar();
139    }
140
141    // MessageListFragment$Callback
142    @Override
143    public void onMessageOpen(long messageId, long messageMailboxId, long listMailboxId,
144            int type) {
145        if (type == MessageListFragment.Callback.TYPE_DRAFT) {
146            MessageCompose.actionEditDraft(mActivity, messageId);
147        } else {
148            if (getMessageId() != messageId) {
149                navigateToMessage(messageId);
150                mThreePane.showRightPane();
151            }
152        }
153    }
154
155    // MessageListFragment$Callback
156    /**
157     * Apply the auto-advance policy upon initation of a batch command that could potentially
158     * affect the currently selected conversation.
159     */
160    @Override
161    public void onAdvancingOpAccepted(Set<Long> affectedMessages) {
162        if (!isMessageViewInstalled()) {
163            // Do nothing if message view is not visible.
164            return;
165        }
166
167        final MessageOrderManager orderManager = getMessageOrderManager();
168        int autoAdvanceDir = Preferences.getPreferences(mActivity).getAutoAdvanceDirection();
169        if ((autoAdvanceDir == Preferences.AUTO_ADVANCE_MESSAGE_LIST) || (orderManager == null)) {
170            if (affectedMessages.contains(getMessageId())) {
171                goBackToMailbox();
172            }
173            return;
174        }
175
176        // Navigate to the first unselected item in the appropriate direction.
177        switch (autoAdvanceDir) {
178            case Preferences.AUTO_ADVANCE_NEWER:
179                while (affectedMessages.contains(orderManager.getCurrentMessageId())) {
180                    if (!orderManager.moveToNewer()) {
181                        goBackToMailbox();
182                        return;
183                    }
184                }
185                navigateToMessage(orderManager.getCurrentMessageId());
186                break;
187
188            case Preferences.AUTO_ADVANCE_OLDER:
189                while (affectedMessages.contains(orderManager.getCurrentMessageId())) {
190                    if (!orderManager.moveToOlder()) {
191                        goBackToMailbox();
192                        return;
193                    }
194                }
195                navigateToMessage(orderManager.getCurrentMessageId());
196                break;
197        }
198    }
199
200    // MessageListFragment$Callback
201    @Override
202    public boolean onDragStarted() {
203        if (Email.DEBUG) {
204            Log.i(Logging.LOG_TAG, "Drag started");
205        }
206
207        if (((mListContext != null) && mListContext.isSearch())
208                || !mThreePane.isLeftPaneVisible()) {
209            // D&D not allowed.
210            return false;
211        }
212
213        return true;
214    }
215
216    // MessageListFragment$Callback
217    @Override
218    public void onDragEnded() {
219        if (Email.DEBUG) {
220            Log.i(Logging.LOG_TAG, "Drag ended");
221        }
222    }
223
224
225    // MessageViewFragment$Callback
226    @Override
227    public boolean onUrlInMessageClicked(String url) {
228        return ActivityHelper.openUrlInMessage(mActivity, url, getActualAccountId());
229    }
230
231    // MessageViewFragment$Callback
232    @Override
233    public void onLoadMessageStarted() {
234    }
235
236    // MessageViewFragment$Callback
237    @Override
238    public void onLoadMessageFinished() {
239    }
240
241    // MessageViewFragment$Callback
242    @Override
243    public void onLoadMessageError(String errorMessage) {
244    }
245
246    // MessageViewFragment$Callback
247    @Override
248    public void onCalendarLinkClicked(long epochEventStartTime) {
249        ActivityHelper.openCalendar(mActivity, epochEventStartTime);
250    }
251
252    // MessageViewFragment$Callback
253    @Override
254    public void onForward() {
255        MessageCompose.actionForward(mActivity, getMessageId());
256    }
257
258    // MessageViewFragment$Callback
259    @Override
260    public void onReply() {
261        MessageCompose.actionReply(mActivity, getMessageId(), false);
262    }
263
264    // MessageViewFragment$Callback
265    @Override
266    public void onReplyAll() {
267        MessageCompose.actionReply(mActivity, getMessageId(), true);
268    }
269
270    /**
271     * Must be called just after the activity sets up the content view.
272     */
273    @Override
274    public void onActivityViewReady() {
275        super.onActivityViewReady();
276
277        // Set up content
278        mThreePane = (ThreePaneLayout) mActivity.findViewById(R.id.three_pane);
279        mThreePane.setCallback(this);
280
281        mMessageCommandButtons = mThreePane.getMessageCommandButtons();
282        mMessageCommandButtons.setCallback(new CommandButtonCallback());
283        mInMessageCommandButtons = mThreePane.getInMessageCommandButtons();
284        mInMessageCommandButtons.setCallback(new CommandButtonCallback());
285    }
286
287    @Override
288    protected ActionBarController createActionBarController(Activity activity) {
289        return new ActionBarController(activity, activity.getLoaderManager(),
290                activity.getActionBar(), new ActionBarControllerCallback());
291    }
292
293    /**
294     * @return the currently selected account ID, *or* {@link Account#ACCOUNT_ID_COMBINED_VIEW}.
295     *
296     * @see #getActualAccountId()
297     */
298    @Override
299    public long getUIAccountId() {
300        return isMailboxListInstalled() ? getMailboxListFragment().getAccountId()
301                :Account.NO_ACCOUNT;
302    }
303
304    @Override
305    public long getMailboxSettingsMailboxId() {
306        return getMessageListMailboxId();
307    }
308
309    /**
310     * @return true if refresh is in progress for the current mailbox.
311     */
312    @Override
313    protected boolean isRefreshInProgress() {
314        long messageListMailboxId = getMessageListMailboxId();
315        return (messageListMailboxId >= 0)
316                && mRefreshManager.isMessageListRefreshing(messageListMailboxId);
317    }
318
319    /**
320     * @return true if the UI should enable the "refresh" command.
321     */
322    @Override
323    protected boolean isRefreshEnabled() {
324        return getActualAccountId() != Account.NO_ACCOUNT
325                && (mListContext.getMailboxId() > 0);
326    }
327
328
329    /** {@inheritDoc} */
330    @Override
331    public void onSaveInstanceState(Bundle outState) {
332        super.onSaveInstanceState(outState);
333    }
334
335    /** {@inheritDoc} */
336    @Override
337    public void onRestoreInstanceState(Bundle savedInstanceState) {
338        super.onRestoreInstanceState(savedInstanceState);
339    }
340
341    @Override
342    protected void installMessageListFragment(MessageListFragment fragment) {
343        super.installMessageListFragment(fragment);
344
345        if (isMailboxListInstalled()) {
346            getMailboxListFragment().setHighlightedMailbox(fragment.getMailboxId());
347        }
348        getMessageListFragment().setLayout(mThreePane);
349        mThreePane.setIsSearch(getMessageListFragment().getListContext().isSearch());
350    }
351
352    @Override
353    protected void installMessageViewFragment(MessageViewFragment fragment) {
354        super.installMessageViewFragment(fragment);
355
356        if (isMessageListInstalled()) {
357            getMessageListFragment().setSelectedMessage(fragment.getMessageId());
358        }
359    }
360
361    @Override
362    public void openInternal(final MessageListContext listContext, final long messageId) {
363        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
364            Log.d(Logging.LOG_TAG, this + " open " + listContext);
365        }
366
367        final FragmentTransaction ft = mFragmentManager.beginTransaction();
368        updateMailboxList(ft, true);
369        updateMessageList(ft, true);
370
371        if (messageId != Message.NO_MESSAGE) {
372            updateMessageView(ft, messageId);
373            mThreePane.showRightPane();
374        } else if (mListContext.isSearch() && UiUtilities.showTwoPaneSearchResults(mActivity)) {
375            mThreePane.showRightPane();
376        } else {
377            mThreePane.showLeftPane();
378        }
379        commitFragmentTransaction(ft);
380    }
381
382    /**
383     * Loads the given account and optionally selects the given mailbox and message. If the
384     * specified account is already selected, no actions will be performed unless
385     * <code>forceReload</code> is <code>true</code>.
386     *
387     * @param ft {@link FragmentTransaction} to use.
388     * @param clearDependentPane if true, the message list and the message view will be cleared
389     */
390    private void updateMailboxList(FragmentTransaction ft, boolean clearDependentPane) {
391        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
392            Log.d(Logging.LOG_TAG, this + " updateMailboxList " + mListContext);
393        }
394
395        long accountId = mListContext.mAccountId;
396        long mailboxId = mListContext.getMailboxId();
397        if ((getUIAccountId() != accountId) || (getMailboxListMailboxId() != mailboxId)) {
398            removeMailboxListFragment(ft);
399            boolean enableHighlight = !mListContext.isSearch();
400            ft.add(mThreePane.getLeftPaneId(),
401                    MailboxListFragment.newInstance(accountId, mailboxId, enableHighlight));
402        }
403        if (clearDependentPane) {
404            removeMessageListFragment(ft);
405            removeMessageViewFragment(ft);
406        }
407    }
408
409    /**
410     * Go back to a mailbox list view. If a message view is currently active, it will
411     * be hidden.
412     */
413    private void goBackToMailbox() {
414        if (isMessageViewInstalled()) {
415            mThreePane.showLeftPane(); // Show mailbox list
416        }
417    }
418
419    /**
420     * Show the message list fragment for the given mailbox.
421     *
422     * @param ft {@link FragmentTransaction} to use.
423     */
424    private void updateMessageList(FragmentTransaction ft, boolean clearDependentPane) {
425        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
426            Log.d(Logging.LOG_TAG, this + " updateMessageList " + mListContext);
427        }
428
429        if (mListContext.getMailboxId() != getMessageListMailboxId()) {
430            removeMessageListFragment(ft);
431            ft.add(mThreePane.getMiddlePaneId(), MessageListFragment.newInstance(mListContext));
432        }
433        if (clearDependentPane) {
434            removeMessageViewFragment(ft);
435        }
436    }
437
438    /**
439     * Shortcut to call {@link #updateMessageList(FragmentTransaction, boolean)} and
440     * commit.
441     */
442    private void updateMessageList(boolean clearDependentPane) {
443        FragmentTransaction ft = mFragmentManager.beginTransaction();
444        updateMessageList(ft, clearDependentPane);
445        commitFragmentTransaction(ft);
446    }
447
448    /**
449     * Show a message on the message view.
450     *
451     * @param ft {@link FragmentTransaction} to use.
452     * @param messageId ID of the mailbox to load. Must never be {@link Message#NO_MESSAGE}.
453     */
454    private void updateMessageView(FragmentTransaction ft, long messageId) {
455        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
456            Log.d(Logging.LOG_TAG, this + " updateMessageView messageId=" + messageId);
457        }
458        if (messageId == Message.NO_MESSAGE) {
459            throw new IllegalArgumentException();
460        }
461
462        if (messageId == getMessageId()) {
463            return; // nothing to do.
464        }
465
466        removeMessageViewFragment(ft);
467
468        ft.add(mThreePane.getRightPaneId(), MessageViewFragment.newInstance(messageId));
469    }
470
471    /**
472     * Shortcut to call {@link #updateMessageView(FragmentTransaction, long)} and commit.
473     */
474    @Override protected void navigateToMessage(long messageId) {
475        FragmentTransaction ft = mFragmentManager.beginTransaction();
476        updateMessageView(ft, messageId);
477        commitFragmentTransaction(ft);
478    }
479
480    /**
481     * Remove the message view if shown.
482     */
483    private void unselectMessage() {
484        commitFragmentTransaction(removeMessageViewFragment(mFragmentManager.beginTransaction()));
485        if (isMessageListInstalled()) {
486            getMessageListFragment().setSelectedMessage(Message.NO_MESSAGE);
487        }
488        stopMessageOrderManager();
489    }
490
491    private class CommandButtonCallback implements MessageCommandButtonView.Callback {
492        @Override
493        public void onMoveToNewer() {
494            moveToNewer();
495        }
496
497        @Override
498        public void onMoveToOlder() {
499            moveToOlder();
500        }
501    }
502
503    /**
504     * Disable/enable the move-to-newer/older buttons.
505     */
506    @Override protected void updateNavigationArrows() {
507        final MessageOrderManager orderManager = getMessageOrderManager();
508        if (orderManager == null) {
509            // shouldn't happen, but just in case
510            mMessageCommandButtons.enableNavigationButtons(false, false, 0, 0);
511            mInMessageCommandButtons.enableNavigationButtons(false, false, 0, 0);
512        } else {
513            mMessageCommandButtons.enableNavigationButtons(
514                    orderManager.canMoveToNewer(), orderManager.canMoveToOlder(),
515                    orderManager.getCurrentPosition(), orderManager.getTotalMessageCount());
516            mInMessageCommandButtons.enableNavigationButtons(
517                    orderManager.canMoveToNewer(), orderManager.canMoveToOlder(),
518                    orderManager.getCurrentPosition(), orderManager.getTotalMessageCount());
519        }
520    }
521
522    /** {@inheritDoc} */
523    @Override
524    public boolean onBackPressed(boolean isSystemBackKey) {
525        if (!mThreePane.isPaneCollapsible()) {
526            if (mActionBarController.onBackPressed(isSystemBackKey)) {
527                return true;
528            }
529
530            if (mThreePane.showLeftPane()) {
531                return true;
532            }
533        }
534
535        if (isMailboxListInstalled() && getMailboxListFragment().navigateUp()) {
536            return true;
537        }
538        return false;
539    }
540
541    @Override
542    protected void onRefresh() {
543        // Cancel previously running instance if any.
544        new RefreshTask(mTaskTracker, mActivity, getActualAccountId(),
545                getMessageListMailboxId()).cancelPreviousAndExecuteParallel();
546    }
547
548    /**
549     * Class to handle refresh.
550     *
551     * When the user press "refresh",
552     * <ul>
553     *   <li>Refresh the current mailbox, if it's refreshable.  (e.g. don't refresh combined inbox,
554     *       drafts, etc.
555     *   <li>Refresh the mailbox list, if it hasn't been refreshed in the last
556     *       {@link #MAILBOX_REFRESH_MIN_INTERVAL}.
557     *   <li>Refresh inbox, if it's not the current mailbox and it hasn't been refreshed in the last
558     *       {@link #INBOX_AUTO_REFRESH_MIN_INTERVAL}.
559     * </ul>
560     */
561    @VisibleForTesting
562    static class RefreshTask extends EmailAsyncTask<Void, Void, Boolean> {
563        private final Clock mClock;
564        private final Context mContext;
565        private final long mAccountId;
566        private final long mMailboxId;
567        private final RefreshManager mRefreshManager;
568        @VisibleForTesting
569        long mInboxId;
570
571        public RefreshTask(EmailAsyncTask.Tracker tracker, Context context, long accountId,
572                long mailboxId) {
573            this(tracker, context, accountId, mailboxId, Clock.INSTANCE,
574                    RefreshManager.getInstance(context));
575        }
576
577        @VisibleForTesting
578        RefreshTask(EmailAsyncTask.Tracker tracker, Context context, long accountId,
579                long mailboxId, Clock clock, RefreshManager refreshManager) {
580            super(tracker);
581            mClock = clock;
582            mContext = context;
583            mRefreshManager = refreshManager;
584            mAccountId = accountId;
585            mMailboxId = mailboxId;
586        }
587
588        /**
589         * Do DB access on a worker thread.
590         */
591        @Override
592        protected Boolean doInBackground(Void... params) {
593            mInboxId = Account.getInboxId(mContext, mAccountId);
594            return Mailbox.isRefreshable(mContext, mMailboxId);
595        }
596
597        /**
598         * Do the actual refresh.
599         */
600        @Override
601        protected void onSuccess(Boolean isCurrentMailboxRefreshable) {
602            if (isCurrentMailboxRefreshable == null) {
603                return;
604            }
605            if (isCurrentMailboxRefreshable) {
606                mRefreshManager.refreshMessageList(mAccountId, mMailboxId, true);
607            }
608            // Refresh mailbox list
609            if (mAccountId != Account.NO_ACCOUNT) {
610                if (shouldRefreshMailboxList()) {
611                    mRefreshManager.refreshMailboxList(mAccountId);
612                }
613            }
614            // Refresh inbox
615            if (shouldAutoRefreshInbox()) {
616                mRefreshManager.refreshMessageList(mAccountId, mInboxId, true);
617            }
618        }
619
620        /**
621         * @return true if the mailbox list of the current account hasn't been refreshed
622         * in the last {@link #MAILBOX_REFRESH_MIN_INTERVAL}.
623         */
624        @VisibleForTesting
625        boolean shouldRefreshMailboxList() {
626            if (mRefreshManager.isMailboxListRefreshing(mAccountId)) {
627                return false;
628            }
629            final long nextRefreshTime = mRefreshManager.getLastMailboxListRefreshTime(mAccountId)
630                    + MAILBOX_REFRESH_MIN_INTERVAL;
631            if (nextRefreshTime > mClock.getTime()) {
632                return false;
633            }
634            return true;
635        }
636
637        /**
638         * @return true if the inbox of the current account hasn't been refreshed
639         * in the last {@link #INBOX_AUTO_REFRESH_MIN_INTERVAL}.
640         */
641        @VisibleForTesting
642        boolean shouldAutoRefreshInbox() {
643            if (mInboxId == mMailboxId) {
644                return false; // Current ID == inbox.  No need to auto-refresh.
645            }
646            if (mRefreshManager.isMessageListRefreshing(mInboxId)) {
647                return false;
648            }
649            final long nextRefreshTime = mRefreshManager.getLastMessageListRefreshTime(mInboxId)
650                    + INBOX_AUTO_REFRESH_MIN_INTERVAL;
651            if (nextRefreshTime > mClock.getTime()) {
652                return false;
653            }
654            return true;
655        }
656    }
657
658    private class ActionBarControllerCallback implements ActionBarController.Callback {
659
660        @Override
661        public long getUIAccountId() {
662            return UIControllerTwoPane.this.getUIAccountId();
663        }
664
665        @Override
666        public long getMailboxId() {
667            return getMessageListMailboxId();
668        }
669
670        @Override
671        public boolean isAccountSelected() {
672            return UIControllerTwoPane.this.isAccountSelected();
673        }
674
675        @Override
676        public void onAccountSelected(long accountId) {
677            switchAccount(accountId, false);
678        }
679
680        @Override
681        public void onMailboxSelected(long accountId, long mailboxId) {
682            openMailbox(accountId, mailboxId);
683        }
684
685        @Override
686        public void onNoAccountsFound() {
687            Welcome.actionStart(mActivity);
688            mActivity.finish();
689        }
690
691        @Override
692        public int getTitleMode() {
693            if (mThreePane.isLeftPaneVisible()) {
694                // Mailbox list visible
695                return TITLE_MODE_ACCOUNT_NAME_ONLY;
696            } else if (mThreePane.isRightPaneVisible()
697                    && !mThreePane.isMiddlePaneVisible()) {
698                return TITLE_MODE_MESSAGE_SUBJECT;
699            } else {
700                // Mailbox list hidden
701                return TITLE_MODE_ACCOUNT_WITH_MAILBOX;
702            }
703        }
704
705        public String getMessageSubject() {
706            if (isMessageViewInstalled() && getMessageViewFragment().isMessageOpen()) {
707                return getMessageViewFragment().getMessage().mSubject;
708            } else {
709                return null;
710            }
711        }
712
713        @Override
714        public boolean shouldShowUp() {
715            final int visiblePanes = mThreePane.getVisiblePanes();
716            final boolean leftPaneHidden = ((visiblePanes & ThreePaneLayout.PANE_LEFT) == 0);
717            return leftPaneHidden
718                    || (isMailboxListInstalled() && getMailboxListFragment().canNavigateUp());
719        }
720
721        @Override
722        public String getSearchHint() {
723            return UIControllerTwoPane.this.getSearchHint();
724        }
725
726        @Override
727        public void onSearchStarted() {
728            UIControllerTwoPane.this.onSearchStarted();
729        }
730
731        @Override
732        public void onSearchSubmit(final String queryTerm) {
733            UIControllerTwoPane.this.onSearchSubmit(queryTerm);
734        }
735
736        @Override
737        public void onSearchExit() {
738            UIControllerTwoPane.this.onSearchExit();
739        }
740    }
741}
742