TwoPaneController.java revision 907e0d8e10457f535b47b9849873f4ab9bec72ad
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.Fragment;
21import android.app.FragmentManager;
22import android.app.FragmentTransaction;
23import android.content.Intent;
24import android.net.Uri;
25import android.os.Bundle;
26import android.view.Gravity;
27import android.widget.FrameLayout;
28
29import com.android.mail.ConversationListContext;
30import com.android.mail.R;
31import com.android.mail.providers.Account;
32import com.android.mail.providers.Conversation;
33import com.android.mail.providers.Folder;
34import com.android.mail.utils.LogUtils;
35import com.android.mail.utils.Utils;
36
37/**
38 * Controller for two-pane Mail activity. Two Pane is used for tablets, where screen real estate
39 * abounds.
40 */
41
42// Called TwoPaneActivityController in Gmail.
43public final class TwoPaneController extends AbstractActivityController {
44    private TwoPaneLayout mLayout;
45
46    /**
47     * @param activity
48     * @param viewMode
49     */
50    public TwoPaneController(MailActivity activity, ViewMode viewMode) {
51        super(activity, viewMode);
52    }
53
54    /**
55     * Display the conversation list fragment.
56     * @param show
57     */
58    private void initializeConversationListFragment(boolean show) {
59        if (show) {
60            if (Intent.ACTION_SEARCH.equals(mActivity.getIntent().getAction())) {
61                if (Utils.showTwoPaneSearchResults(mActivity.getActivityContext())) {
62                    mViewMode.enterSearchResultsConversationMode();
63                } else {
64                    mViewMode.enterSearchResultsListMode();
65                }
66            } else {
67                mViewMode.enterConversationListMode();
68            }
69        }
70        renderConversationList();
71    }
72
73    /**
74     * Render the conversation list in the correct pane.
75     */
76    private void renderConversationList() {
77        if (mActivity == null) {
78            return;
79        }
80        FragmentTransaction fragmentTransaction = mActivity.getFragmentManager().beginTransaction();
81        // Use cross fading animation.
82        fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
83        Fragment conversationListFragment = ConversationListFragment.newInstance(mConvListContext);
84        fragmentTransaction.replace(R.id.conversation_list_pane, conversationListFragment,
85                TAG_CONVERSATION_LIST);
86        fragmentTransaction.commitAllowingStateLoss();
87    }
88
89    /**
90     * Render the folder list in the correct pane.
91     */
92    private void renderFolderList() {
93        if (mActivity == null) {
94            return;
95        }
96        createFolderListFragment(null, mAccount.folderListUri);
97    }
98
99    private void createFolderListFragment(Folder parent, Uri uri) {
100        FolderListFragment folderListFragment = FolderListFragment.newInstance(parent, uri);
101        FragmentTransaction fragmentTransaction = mActivity.getFragmentManager().beginTransaction();
102        fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
103        fragmentTransaction.replace(R.id.content_pane, folderListFragment, TAG_FOLDER_LIST);
104        fragmentTransaction.commitAllowingStateLoss();
105        // Since we are showing the folder list, we are at the start of the view
106        // stack.
107        resetActionBarIcon();
108    }
109
110    @Override
111    protected boolean isConversationListVisible() {
112        return !mLayout.isConversationListCollapsed();
113    }
114
115    @Override
116    public void showConversationList(ConversationListContext listContext) {
117        super.showConversationList(listContext);
118        initializeConversationListFragment(true);
119    }
120
121    @Override
122    public void showFolderList() {
123        // On two-pane layouts, showing the folder list takes you to the top level of the
124        // application, which is the same as pressing the Up button
125        onUpPressed();
126    }
127
128    @Override
129    public boolean onCreate(Bundle savedState) {
130        mActivity.setContentView(R.layout.two_pane_activity);
131        mLayout = (TwoPaneLayout) mActivity.findViewById(R.id.two_pane_activity);
132        if (mLayout == null) {
133            // We need the layout for everything. Crash early if it is null.
134            LogUtils.wtf(LOG_TAG, "mLayout is null!");
135        }
136        mLayout.initializeLayout(mActivity.getApplicationContext(),
137                Intent.ACTION_SEARCH.equals(mActivity.getIntent().getAction()));
138
139        // The tablet layout needs to refer to mode changes.
140        mViewMode.addListener(mLayout);
141        // The activity controller needs to listen to layout changes.
142        mLayout.setListener(this);
143        final boolean isParentInitialized = super.onCreate(savedState);
144        return isParentInitialized;
145    }
146
147    @Override
148    public void onWindowFocusChanged(boolean hasFocus) {
149        if (hasFocus && !mLayout.isConversationListCollapsed()) {
150            // The conversation list is visible.
151            Utils.setConversationCursorVisibility(mConversationListCursor, true);
152        }
153    }
154
155    @Override
156    public void onAccountChanged(Account account) {
157        super.onAccountChanged(account);
158        renderFolderList();
159    }
160
161    @Override
162    public void onFolderChanged(Folder folder) {
163        super.onFolderChanged(folder);
164        exitCabMode();
165        final FolderListFragment folderList = getFolderListFragment();
166        if (folderList != null) {
167            folderList.selectInitialFolder(folder);
168        }
169    }
170
171    @Override
172    public void onFolderSelected(Folder folder) {
173        super.onFolderSelected(folder);
174        if (folder.hasChildren) {
175            // Replace this fragment with a new FolderListFragment
176            // showing this folder's children if we are not already looking
177            // at the child view for this folder.
178            createFolderListFragment(folder, folder.childFoldersListUri);
179            // Show the up affordance when digging into child folders.
180            mActionBarView.setBackButton();
181            return;
182        }
183        final FolderListFragment folderList = getFolderListFragment();
184        if (folderList != null) {
185            folderList.selectInitialFolder(folder);
186        }
187    }
188
189    private void goUpFolderHierarchy(Folder current) {
190        Folder parent = current.parent;
191        if (parent.parent != null) {
192            super.onFolderSelected(parent);
193            createFolderListFragment(parent.parent, parent.parent.childFoldersListUri);
194            // Show the up affordance when digging into child folders.
195            mActionBarView.setBackButton();
196        } else {
197            onFolderSelected(parent);
198        }
199        final FolderListFragment folderList = getFolderListFragment();
200        if (folderList != null) {
201            folderList.selectInitialFolder(parent);
202        }
203    }
204
205    @Override
206    public void onViewModeChanged(int newMode) {
207        super.onViewModeChanged(newMode);
208        if (newMode != ViewMode.WAITING_FOR_ACCOUNT_INITIALIZATION) {
209            // Clear the wait fragment
210            hideWaitForInitialization();
211        }
212        // In conversation mode, if the conversation list is not visible, then the user cannot
213        // see the selected conversations. Disable the CAB mode while leaving the selected set
214        // untouched.
215        // Otherwise, the conversation list is guaranteed to be visible. Try to enable the CAB
216        // mode if any conversations are selected.
217        if (newMode == ViewMode.CONVERSATION){
218            enableOrDisableCab();
219        }
220        resetActionBarIcon();
221    }
222
223    @Override
224    public void onConversationVisibilityChanged(boolean visible) {
225        super.onConversationVisibilityChanged(visible);
226        if (!visible) {
227            mPagerController.hide();
228        }
229    }
230
231    @Override
232    public void onConversationListVisibilityChanged(boolean visible) {
233        super.onConversationListVisibilityChanged(visible);
234    }
235
236    @Override
237    public void resetActionBarIcon() {
238        if (mViewMode.isListMode()) {
239            mActionBarView.removeBackButton();
240        } else {
241            mActionBarView.setBackButton();
242        }
243    }
244
245    /**
246     * Enable or disable the CAB mode based on the visibility of the conversation list fragment.
247     */
248    private final void enableOrDisableCab() {
249        if (mLayout.isConversationListCollapsed()) {
250            disableCabMode();
251        } else {
252            enableCabMode();
253        }
254    }
255
256    @Override
257    public void showConversation(Conversation conversation) {
258        super.showConversation(conversation);
259        if (mActivity == null) {
260            return;
261        }
262        if (conversation == null) {
263            // This is a request to remove the conversation view and show the conversation list
264            // fragment instead.
265            onBackPressed();
266            return;
267        }
268        // If conversation list is not visible, then the user cannot see the CAB mode, so exit it.
269        // This is needed here (in addition to during viewmode changes) because orientation changes
270        // while viewing a conversation don't change the viewmode: the mode stays
271        // ViewMode.CONVERSATION and yet the conversation list goes in and out of visibility.
272        enableOrDisableCab();
273
274        final int mode = mViewMode.getMode();
275        if (mode == ViewMode.SEARCH_RESULTS_LIST || mode == ViewMode.SEARCH_RESULTS_CONVERSATION) {
276            mViewMode.enterSearchResultsConversationMode();
277        } else {
278            mViewMode.enterConversationMode();
279        }
280        mPagerController.show(mAccount, mFolder, conversation);
281        final ConversationListFragment convList = getConversationListFragment();
282        if (convList != null) {
283            LogUtils.d(LOG_TAG, "showConversation: Selecting position %d.", conversation.position);
284            convList.setSelected(conversation.position);
285        }
286    }
287
288    @Override
289    public void showWaitForInitialization() {
290        super.showWaitForInitialization();
291
292        Fragment waitFragment = WaitFragment.newInstance(mAccount);
293        FragmentTransaction fragmentTransaction = mActivity.getFragmentManager().beginTransaction();
294        fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
295        fragmentTransaction.replace(R.id.two_pane_activity, waitFragment, TAG_WAIT);
296        fragmentTransaction.commitAllowingStateLoss();
297    }
298
299    @Override
300    public void hideWaitForInitialization() {
301        final FragmentManager manager = mActivity.getFragmentManager();
302        final WaitFragment waitFragment = (WaitFragment)manager.findFragmentByTag(TAG_WAIT);
303        if (waitFragment != null) {
304            FragmentTransaction fragmentTransaction =
305                    mActivity.getFragmentManager().beginTransaction();
306            fragmentTransaction.remove(waitFragment);
307            fragmentTransaction.commitAllowingStateLoss();
308        }
309    }
310
311    /**
312     * Up works as follows:
313     * 1) If the user is in a conversation and:
314     *  a) the conversation list is hidden (portrait mode), shows the conv list and
315     *  stays in conversation view mode.
316     *  b) the conversation list is shown, goes back to conversation list mode.
317     * 2) If the user is in search results, up exits search.
318     * mode and returns the user to whatever view they were in when they began search.
319     * 3) If the user is in conversation list mode, there is no up.
320     */
321    @Override
322    public boolean onUpPressed() {
323        int mode = mViewMode.getMode();
324        if (mode == ViewMode.CONVERSATION) {
325            if (mLayout.isConversationListCollapsed()) {
326                commitLeaveBehindItems();
327            }
328            mActivity.onBackPressed();
329        } else if (mode == ViewMode.SEARCH_RESULTS_CONVERSATION) {
330            if (mLayout.isConversationListCollapsed()
331                    || (mConvListContext.isSearchResult() && !Utils
332                            .showTwoPaneSearchResults
333                                (mActivity.getApplicationContext()))) {
334                commitLeaveBehindItems();
335                onBackPressed();
336            } else {
337                mActivity.finish();
338            }
339        } else if (mode == ViewMode.SEARCH_RESULTS_LIST) {
340            mActivity.finish();
341        } else if (mode == ViewMode.CONVERSATION_LIST) {
342            popView(true);
343        }
344        return true;
345    }
346
347    @Override
348    public boolean onBackPressed() {
349        // Clear any visible undo bars.
350        mUndoBarView.hide(false);
351        popView(false);
352        return true;
353    }
354
355    /**
356     * Pops the "view stack" to the last screen the user was viewing.
357     *
358     * @param preventClose Whether to prevent closing the app if the stack is empty.
359     */
360    protected void popView(boolean preventClose) {
361        // If the user is in search query entry mode, or the user is viewing
362        // search results, exit
363        // the mode.
364        int mode = mViewMode.getMode();
365        if (mode == ViewMode.SEARCH_RESULTS_LIST) {
366            mActivity.finish();
367        } else if (mode == ViewMode.CONVERSATION) {
368            // Go to conversation list.
369            mViewMode.enterConversationListMode();
370        } else if (mode == ViewMode.SEARCH_RESULTS_CONVERSATION) {
371            mViewMode.enterSearchResultsListMode();
372        } else {
373            if (mode == ViewMode.CONVERSATION_LIST && getFolderListFragment().showingHierarchy()) {
374                // If the user navigated via the left folders list into a child folder,
375                // back should take the user up to the parent folder's conversation list.
376                if (mFolder.parent != null) {
377                    goUpFolderHierarchy(mFolder);
378                } else  {
379                    // Show inbox; we are at the top of the hierarchy we were
380                    // showing, and it doesn't have a parent, so we must want to
381                    // the basic account folder list.
382                    createFolderListFragment(null, mAccount.folderListUri);
383                    loadAccountInbox();
384                }
385            } else if (!preventClose) {
386                // There is nothing else to pop off the stack.
387                mActivity.finish();
388            }
389        }
390    }
391
392    @Override
393    public void exitSearchMode() {
394        int mode = mViewMode.getMode();
395        if (mode == ViewMode.SEARCH_RESULTS_LIST
396                || (mode == ViewMode.SEARCH_RESULTS_CONVERSATION
397                        && Utils.showTwoPaneSearchResults(mActivity.getApplicationContext()))) {
398            mActivity.finish();
399        }
400    }
401
402    @Override
403    public boolean shouldShowFirstConversation() {
404        return Intent.ACTION_SEARCH.equals(mActivity.getIntent().getAction())
405                && Utils.showTwoPaneSearchResults(mActivity.getApplicationContext());
406    }
407
408    @Override
409    public void onUndoAvailable(UndoOperation op) {
410        final int mode = mViewMode.getMode();
411        final FrameLayout.LayoutParams params;
412        final ConversationListFragment convList = getConversationListFragment();
413        switch (mode) {
414            case ViewMode.SEARCH_RESULTS_LIST:
415            case ViewMode.CONVERSATION_LIST:
416                params = (FrameLayout.LayoutParams) mUndoBarView.getLayoutParams();
417                params.width = mLayout.computeConversationListWidth();
418                params.gravity = Gravity.BOTTOM | Gravity.RIGHT;
419                mUndoBarView.setLayoutParams(params);
420                if (convList != null) {
421                    mUndoBarView.show(true, mActivity.getActivityContext(), op, mAccount,
422                        convList.getAnimatedAdapter(), mConversationListCursor);
423                }
424                break;
425            case ViewMode.SEARCH_RESULTS_CONVERSATION:
426            case ViewMode.CONVERSATION:
427                if (op.mBatch) {
428                    // Show undo bar in the conversation list.
429                    params = (FrameLayout.LayoutParams) mUndoBarView.getLayoutParams();
430                    params.gravity = Gravity.BOTTOM | Gravity.LEFT;
431                    params.width = mLayout.computeConversationListWidth();
432                } else {
433                    // Show undo bar in the conversation.
434                    params = (FrameLayout.LayoutParams) mUndoBarView.getLayoutParams();
435                    params.gravity = Gravity.BOTTOM | Gravity.RIGHT;
436                    params.width = mLayout.getConversationView().getWidth();
437                }
438                mUndoBarView.setLayoutParams(params);
439                mUndoBarView.show(true, mActivity.getActivityContext(), op, mAccount,
440                        convList.getAnimatedAdapter(), mConversationListCursor);
441                break;
442        }
443    }
444}
445