blob: 950844982b2b1d5099ab7677ec77324aa1f44c45 [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2012 Google Inc.
* Licensed to The Android Open Source Project.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package com.android.mail.ui;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.ActionBarDrawerToggle;
import android.support.v4.widget.DrawerLayout;
import android.view.MenuItem;
import android.view.View;
import android.view.Gravity;
import android.view.ViewGroup;
import android.widget.ListView;
import com.android.mail.ConversationListContext;
import com.android.mail.R;
import com.android.mail.providers.Account;
import com.android.mail.providers.Conversation;
import com.android.mail.providers.Folder;
import com.android.mail.providers.UIProvider;
import com.android.mail.utils.LogUtils;
import com.android.mail.utils.Utils;
/**
* Controller for one-pane Mail activity. One Pane is used for phones, where screen real estate is
* limited. This controller also does the layout, since the layout is simpler in the one pane case.
*/
// Called OnePaneActivityController in Gmail.
public final class OnePaneController extends AbstractActivityController {
/** Key used to store {@link #mLastFolderListTransactionId}. */
private static final String FOLDER_LIST_TRANSACTION_KEY = "folder-list-transaction";
/** Key used to store {@link #mLastInboxConversationListTransactionId} */
private static final String INBOX_CONVERSATION_LIST_TRANSACTION_KEY =
"inbox_conversation-list-transaction";
/** Key used to store {@link #mLastConversationListTransactionId} */
private static final String CONVERSATION_LIST_TRANSACTION_KEY = "conversation-list-transaction";
/** Key used to store {@link #mLastConversationTransactionId}. */
private static final String CONVERSATION_TRANSACTION_KEY = "conversation-transaction";
/** Key used to store {@link #mConversationListVisible}. */
private static final String CONVERSATION_LIST_VISIBLE_KEY = "conversation-list-visible";
/** Key used to store {@link #mConversationListNeverShown}. */
private static final String CONVERSATION_LIST_NEVER_SHOWN_KEY = "conversation-list-never-shown";
/** Key to store {@link #mInbox}. */
private final static String SAVED_INBOX_KEY = "m-inbox";
private static final int INVALID_ID = -1;
private boolean mConversationListVisible = false;
private int mLastInboxConversationListTransactionId = INVALID_ID;
private int mLastConversationListTransactionId = INVALID_ID;
private int mLastConversationTransactionId = INVALID_ID;
private int mLastFolderListTransactionId = INVALID_ID;
private Folder mInbox;
/** Whether a conversation list for this account has ever been shown.*/
private boolean mConversationListNeverShown = true;
private boolean mHasNewAccountOrFolder = false;
private DrawerLayout mDrawerContainer;
private ViewGroup mDrawerPullout;
private ListView mListViewForAnimating;
private ActionBarDrawerToggle mDrawerToggle;
private class MailDrawerListener implements DrawerLayout.DrawerListener {
@Override
public void onDrawerOpened(View drawerView) {
mDrawerToggle.onDrawerOpened(drawerView);
}
@Override
public void onDrawerClosed(View drawerView) {
mDrawerToggle.onDrawerClosed(drawerView);
if (mHasNewAccountOrFolder) {
refreshDrawer();
}
}
/**
* As part of the overriden function, it will animate the alpha of the conversation list
* view along with the drawer sliding when we're in the process of switching accounts or
* folders. Note, this is the same amount of work done as {@link ValueAnimator#ofFloat}.
*/
@Override
public void onDrawerSlide(View drawerView, float slideOffset) {
mDrawerToggle.onDrawerSlide(drawerView, slideOffset);
if (mHasNewAccountOrFolder && mListViewForAnimating != null) {
mListViewForAnimating.setAlpha(slideOffset);
LogUtils.w(LOG_TAG, "OFFSET: " + slideOffset);
}
}
/**
* This condition here should only be called when the drawer is stuck in a weird state
* and doesn't register the onDrawerClosed, but shows up as idle. Make sure to refresh
* and, more importantly, unlock the drawer when this is the case.
*/
@Override
public void onDrawerStateChanged(int newState) {
mDrawerToggle.onDrawerStateChanged(newState);
if (mHasNewAccountOrFolder && newState == DrawerLayout.STATE_IDLE) {
refreshDrawer();
}
}
/**
* If we've reached a stable drawer state, unlock the drawer for usage, clear the
* conversation list, and finish end actions. Also, make
* {@link OnePaneController#mHasNewAccountOrFolder} false to reflect we're done changing.
*/
public void refreshDrawer() {
mHasNewAccountOrFolder = false;
mDrawerContainer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
ConversationListFragment conversationList = getConversationListFragment();
if (conversationList != null) {
conversationList.clear();
}
notifyDrawerClosed();
}
}
public OnePaneController(MailActivity activity, ViewMode viewMode) {
super(activity, viewMode);
}
@Override
public void onRestoreInstanceState(Bundle inState) {
super.onRestoreInstanceState(inState);
if (inState == null) {
return;
}
mLastFolderListTransactionId = inState.getInt(FOLDER_LIST_TRANSACTION_KEY, INVALID_ID);
mLastInboxConversationListTransactionId =
inState.getInt(INBOX_CONVERSATION_LIST_TRANSACTION_KEY, INVALID_ID);
mLastConversationListTransactionId =
inState.getInt(CONVERSATION_LIST_TRANSACTION_KEY, INVALID_ID);
mLastConversationTransactionId = inState.getInt(CONVERSATION_TRANSACTION_KEY, INVALID_ID);
mConversationListVisible = inState.getBoolean(CONVERSATION_LIST_VISIBLE_KEY);
mConversationListNeverShown = inState.getBoolean(CONVERSATION_LIST_NEVER_SHOWN_KEY);
mInbox = inState.getParcelable(SAVED_INBOX_KEY);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(FOLDER_LIST_TRANSACTION_KEY, mLastFolderListTransactionId);
outState.putInt(INBOX_CONVERSATION_LIST_TRANSACTION_KEY,
mLastInboxConversationListTransactionId);
outState.putInt(CONVERSATION_LIST_TRANSACTION_KEY, mLastConversationListTransactionId);
outState.putInt(CONVERSATION_TRANSACTION_KEY, mLastConversationTransactionId);
outState.putBoolean(CONVERSATION_LIST_VISIBLE_KEY, mConversationListVisible);
outState.putBoolean(CONVERSATION_LIST_NEVER_SHOWN_KEY, mConversationListNeverShown);
outState.putParcelable(SAVED_INBOX_KEY, mInbox);
}
@Override
public void resetActionBarIcon() {
// Calling resetActionBarIcon should never remove the up affordance
// even when waiting for sync (Folder list should still show with one
// account. Currently this method is blank to avoid any changes.
}
/**
* Returns true if the candidate URI is the URI for the default inbox for the given account.
* @param candidate the URI to check
* @param account the account whose default Inbox the candidate might be
* @return true if the candidate is indeed the default inbox for the given account.
*/
private static boolean isDefaultInbox(Uri candidate, Account account) {
return (candidate != null && account != null)
&& candidate.equals(account.settings.defaultInbox);
}
/**
* Returns true if the user is currently in the conversation list view, viewing the default
* inbox.
* @return true if user is in conversation list mode, viewing the default inbox.
*/
private static boolean inInbox(final Account account, final ConversationListContext context) {
// If we don't have valid state, then we are not in the inbox.
return !(account == null || context == null || context.folder == null
|| account.settings == null) && !ConversationListContext.isSearchResult(context)
&& isDefaultInbox(context.folder.uri, account);
}
/**
* On account change, carry out super implementation, load FolderListFragment
* into drawer (to avoid repetitive calls to replaceFragment).
*/
@Override
public void changeAccount(Account account) {
super.changeAccount(account);
mConversationListNeverShown = true;
closeDrawerIfOpen();
}
@Override
public boolean onCreate(Bundle savedInstanceState) {
mActivity.setContentView(R.layout.one_pane_activity);
mDrawerContainer = (DrawerLayout) mActivity.findViewById(R.id.drawer_container);
mDrawerContainer.setScrimColor(
mContext.getResources().getColor(R.color.drawer_background_dim));
mDrawerContainer.setDrawerListener(new MailDrawerListener());
mDrawerPullout = (ViewGroup) mDrawerContainer.findViewById(R.id.drawer_pullout);
mDrawerContainer.setDrawerShadow(
mContext.getResources().getDrawable(R.drawable.drawer_shadow), Gravity.LEFT);
mDrawerPullout.setBackgroundResource(R.color.list_background_color);
mDrawerToggle = new ActionBarDrawerToggle((Activity)mActivity, mDrawerContainer,
R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close);
// The parent class sets the correct viewmode and starts the application off.
return super.onCreate(savedInstanceState);
}
@Override
public void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Sync the toggle state after onRestoreInstanceState has occurred.
mDrawerToggle.syncState();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
/*
* The action bar home/up action should open or close the drawer.
* mDrawerToggle will take care of this.
*/
if (mDrawerToggle.onOptionsItemSelected(item)) {
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mDrawerToggle.onConfigurationChanged(newConfig);
}
@Override
protected boolean isConversationListVisible() {
return mConversationListVisible;
}
/**
* If drawer is open/visible (even partially), close it.
*/
private void closeDrawerIfOpen() {
if(mDrawerContainer.isDrawerOpen(mDrawerPullout)) {
mDrawerContainer.closeDrawers();
}
}
@Override
public void onViewModeChanged(int newMode) {
super.onViewModeChanged(newMode);
// When view mode changes, lock drawer if viewing search results or
// viewing a conversation. Set unlocked otherwise.
if (ViewMode.isSearchMode(newMode) || ViewMode.isConversationMode(newMode)) {
mDrawerToggle.setDrawerIndicatorEnabled(false);
mDrawerContainer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
} else {
mDrawerToggle.setDrawerIndicatorEnabled(true);
mDrawerContainer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
}
closeDrawerIfOpen();
// When entering conversation list mode, hide and clean up any currently visible
// conversation.
if (ViewMode.isListMode(newMode)) {
mPagerController.hide(true /* changeVisibility */);
}
// When we step away from the conversation mode, we don't have a current conversation
// anymore. Let's blank it out so clients calling getCurrentConversation are not misled.
if (!ViewMode.isConversationMode(newMode)) {
setCurrentConversation(null);
}
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder(super.toString());
sb.append("{lastFolderListTransId=");
sb.append(mLastFolderListTransactionId);
sb.append(" lastInboxTransId=");
sb.append(mLastInboxConversationListTransactionId);
sb.append(" lastConvListTransId=");
sb.append(mLastConversationListTransactionId);
sb.append("}");
return sb.toString();
}
@Override
public void showConversationList(ConversationListContext listContext) {
super.showConversationList(listContext);
enableCabMode();
if (ConversationListContext.isSearchResult(listContext)) {
mViewMode.enterSearchResultsListMode();
} else {
mViewMode.enterConversationListMode();
}
final int transition = mConversationListNeverShown
? FragmentTransaction.TRANSIT_FRAGMENT_FADE
: FragmentTransaction.TRANSIT_FRAGMENT_OPEN;
Fragment conversationListFragment = ConversationListFragment.newInstance(listContext);
if (!inInbox(mAccount, listContext)) {
// Maintain fragment transaction history so we can get back to the
// fragment used to launch this list.
mLastConversationListTransactionId = replaceFragmentWithBack(conversationListFragment,
transition, TAG_CONVERSATION_LIST, R.id.content_pane);
} else {
// If going to the inbox, clear the folder list transaction history.
mInbox = listContext.folder;
mLastInboxConversationListTransactionId = replaceFragmentWithBack(
conversationListFragment, transition, TAG_CONVERSATION_LIST, R.id.content_pane);
// FIXME: "forgetting" the folder list entry in the back stack doesn't remove it from
// the back stack. only popping past it can remove it.
// Maybe we should clear the back stack prior to opening a folder.
mLastFolderListTransactionId = INVALID_ID;
// If we ever to to the inbox, we want to unset the transation id for any other
// non-inbox folder.
mLastConversationListTransactionId = INVALID_ID;
}
mActivity.getFragmentManager().executePendingTransactions();
mConversationListVisible = true;
onConversationVisibilityChanged(false);
onConversationListVisibilityChanged(true);
mConversationListNeverShown = false;
}
@Override
protected void showConversation(Conversation conversation, boolean inLoaderCallbacks) {
super.showConversation(conversation, inLoaderCallbacks);
if (conversation == null) {
transitionBackToConversationListMode(inLoaderCallbacks);
return;
}
disableCabMode();
if (ConversationListContext.isSearchResult(mConvListContext)) {
mViewMode.enterSearchResultsConversationMode();
} else {
mViewMode.enterConversationMode();
}
final FragmentManager fm = mActivity.getFragmentManager();
final FragmentTransaction ft = fm.beginTransaction();
ft.addToBackStack(null);
// Switching to conversation view is an incongruous transition:
// we are not replacing a fragment with another fragment as
// usual. Instead, reveal the heretofore inert conversation
// ViewPager and just remove the previously visible fragment
// e.g. conversation list, or possibly label list?).
final Fragment f = fm.findFragmentById(R.id.content_pane);
// FragmentManager#findFragmentById can return fragments that are not added to the activity.
// We want to make sure that we don't attempt to remove fragments that are not added to the
// activity, as when the transaction is popped off, the FragmentManager will attempt to
// readd the same fragment twice
if (f != null && f.isAdded()) {
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.remove(f);
ft.commitAllowingStateLoss();
fm.executePendingTransactions();
}
mPagerController.show(mAccount, mFolder, conversation, true /* changeVisibility */);
onConversationVisibilityChanged(true);
mConversationListVisible = false;
onConversationListVisibilityChanged(false);
}
@Override
public void showWaitForInitialization() {
super.showWaitForInitialization();
replaceFragment(getWaitFragment(), FragmentTransaction.TRANSIT_FRAGMENT_OPEN, TAG_WAIT,
R.id.content_pane);
}
@Override
protected void hideWaitForInitialization() {
transitionToInbox();
super.hideWaitForInitialization();
}
@Override
public boolean doesActionChangeConversationListVisibility(int action) {
switch (action) {
case R.id.archive:
case R.id.remove_folder:
case R.id.delete:
case R.id.discard_drafts:
case R.id.mark_important:
case R.id.mark_not_important:
case R.id.mute:
case R.id.report_spam:
case R.id.mark_not_spam:
case R.id.report_phishing:
case R.id.refresh:
case R.id.change_folder:
return false;
default:
return true;
}
}
/**
* Toggles the drawer pullout. If it was open (Fully extended), the
* drawer will be closed. Otherwise, the drawer will be opened. This should
* only be called when used with a toggle item. Other cases should be handled
* explicitly with just closeDrawers() or openDrawer(View drawerView);
*/
@Override
protected void toggleFolderListState() {
if(mDrawerContainer.isDrawerOpen(mDrawerPullout)) {
mDrawerContainer.closeDrawers();
} else {
mDrawerContainer.openDrawer(mDrawerPullout);
}
}
@Override
public void onFolderChanged(Folder folder) {
mDrawerContainer.closeDrawers();
super.onFolderChanged(folder);
}
/**
* Replace the content_pane with the fragment specified here. The tag is specified so that
* the {@link ActivityController} can look up the fragments through the
* {@link android.app.FragmentManager}. This action will be placed on the back stack.
* @param fragment the new fragment to put
* @param transition the transition to show
* @param tag a tag for the fragment manager.
* @param anchor ID of view to replace fragment in
* @return transaction ID returned when the transition is committed.
*/
private int replaceFragmentWithBack(Fragment fragment, int transition, String tag, int anchor) {
return replaceFragment(fragment, transition, tag, anchor, true);
}
// (Not on the back stack -> no transaction ID to return.)
private void replaceFragment(Fragment fragment, int transition, String tag, int anchor) {
replaceFragment(fragment, transition, tag, anchor, false);
}
private int replaceFragment(Fragment fragment, int transition, String tag, int anchor,
boolean addToBackStack) {
final FragmentManager fm = mActivity.getFragmentManager();
FragmentTransaction fragmentTransaction = fm.beginTransaction();
fragmentTransaction.setTransition(transition);
if (addToBackStack) {
fragmentTransaction.addToBackStack(null);
}
fragmentTransaction.replace(anchor, fragment, tag);
final int id = fragmentTransaction.commitAllowingStateLoss();
fm.executePendingTransactions();
return id;
}
/**
* Back works as follows:
* 1) If the drawer is pulled out (Or mid-drag), close it - handled.
* 2) If the user is in the folder list view, go back
* to the account default inbox.
* 3) If the user is in a conversation list
* that is not the inbox AND:
* a) they got there by going through the folder
* list view, go back to the folder list view.
* b) they got there by using some other means (account dropdown), go back to the inbox.
* 4) If the user is in a conversation, go back to the conversation list they were last in.
* 5) If the user is in the conversation list for the default account inbox,
* back exits the app.
*/
@Override
public boolean handleBackPress() {
final int mode = mViewMode.getMode();
if (mDrawerContainer.isDrawerVisible(mDrawerPullout)) {
mDrawerContainer.closeDrawers();
return true;
}
//TODO(shahrk): Remove the folder list standalone view
if (mode == ViewMode.FOLDER_LIST) {
final Folder hierarchyFolder = getHierarchyFolder();
final FolderListFragment folderListFragment = getFolderListFragment();
final boolean parentHasChildren = folderListFragment != null &&
folderListFragment.showingHierarchy() && hierarchyFolder != null
&& hierarchyFolder.parent != null;
if (parentHasChildren) {
// If we are showing the folder list and the user is exploring
// the children of a single parent folder,
// back should display the parent folder's parent and siblings.
goUpFolderHierarchy(hierarchyFolder);
} else {
// We are at the topmost list of folders: go back
mLastFolderListTransactionId = INVALID_ID;
transitionToInbox();
}
} else if (mode == ViewMode.SEARCH_RESULTS_LIST) {
mActivity.finish();
} else if (mViewMode.isListMode() && !inInbox(mAccount, mConvListContext)) {
if (mLastFolderListTransactionId != INVALID_ID) {
// If the user got here by navigating via the folder list, back
// should bring them back to the folder list.
mViewMode.enterFolderListMode();
mActivity.getFragmentManager().popBackStack(mLastFolderListTransactionId, 0);
} else {
transitionToInbox();
}
} else if (mViewMode.isConversationMode()) {
transitionBackToConversationListMode(false /* inLoaderCallbacks */);
} else {
mActivity.finish();
}
mToastBar.hide(false);
return true;
}
private void goUpFolderHierarchy(Folder current) {
Folder top = current.parent;
if (top != null) {
// FIXME: This is silly. we worked so hard to add folder fragments to the back stack.
// it should either just pop back, or should not use the back stack at all.
setHierarchyFolder(top);
// Replace this fragment with a new FolderListFragment
// showing this folder's children if we are not already
// looking at the child view for this folder.
mLastFolderListTransactionId = replaceFragmentWithBack(
FolderListFragment.ofTree(top, false),
FragmentTransaction.TRANSIT_FRAGMENT_OPEN, TAG_FOLDER_LIST, R.id.content_pane);
} else {
// Otherwise, clear the selected folder and go back to whatever the
// last folder list displayed was.
// TODO(viki): Load folder list for parent folder.
}
}
/**
* Switch to the Inbox by creating a new conversation list context that loads the inbox.
*/
private void transitionToInbox() {
// The inbox could have changed, in which case we should load it again.
if (mInbox == null || !isDefaultInbox(mInbox.uri, mAccount)) {
loadAccountInbox();
} else {
onFolderChanged(mInbox);
}
}
@Override
public void onFolderSelected(Folder folder) {
if (folder.hasChildren && !folder.equals(getHierarchyFolder())) {
mViewMode.enterFolderListMode();
setHierarchyFolder(folder);
// Replace this fragment with a new FolderListFragment
// showing this folder's children if we are not already
// looking at the child view for this folder.
mLastFolderListTransactionId = replaceFragmentWithBack(
FolderListFragment.ofTree(folder, false),
FragmentTransaction.TRANSIT_FRAGMENT_OPEN, TAG_FOLDER_LIST, R.id.content_pane);
} else {
super.onFolderSelected(folder);
}
}
private static boolean isTransactionIdValid(int id) {
return id >= 0;
}
/**
* Up works as follows:
* 1) If the user is in a conversation list that is not the default account inbox,
* a conversation, or the folder list, up follows the rules of back.
* 2) If the user is in search results, up exits search
* mode and returns the user to whatever view they were in when they began search.
* 3) If the user is in the inbox, there is no up.
*/
@Override
public boolean handleUpPress() {
final int mode = mViewMode.getMode();
if (mode == ViewMode.SEARCH_RESULTS_LIST) {
mActivity.finish();
// Not needed, the activity is going away anyway.
return true;
}
if (mode == ViewMode.CONVERSATION_LIST
|| mode == ViewMode.WAITING_FOR_ACCOUNT_INITIALIZATION) {
// Up affordance: show the drawer.
toggleFolderListState();
return true;
}
if (mode == ViewMode.CONVERSATION
|| mode == ViewMode.SEARCH_RESULTS_CONVERSATION) {
// Same as go back.
handleBackPress();
return true;
}
if (mode == ViewMode.FOLDER_LIST) {
// If the folder is the top level of an heirarchy or flat, toggle the
// state of the drawer. Otherwise, let it pass on as a back press.
// We don't want this code in back press, otherwise pressing back
// will exhibit the same behavior
final FolderListFragment folderListFragment = getFolderListFragment();
if (folderListFragment != null) {
final Folder parentFolder = folderListFragment.getParentFolder();
if (parentFolder == null || parentFolder.parent == null) {
toggleFolderListState();
return true;
}
}
handleBackPress();
}
return true;
}
private void transitionBackToConversationListMode(boolean inLoaderCallbacks) {
final int mode = mViewMode.getMode();
enableCabMode();
if (mode == ViewMode.SEARCH_RESULTS_CONVERSATION) {
mViewMode.enterSearchResultsListMode();
} else {
mViewMode.enterConversationListMode();
}
if (isTransactionIdValid(mLastConversationListTransactionId)) {
safelyPopBackStack(mLastConversationListTransactionId, inLoaderCallbacks);
} else if (isTransactionIdValid(mLastInboxConversationListTransactionId)) {
safelyPopBackStack(mLastInboxConversationListTransactionId, inLoaderCallbacks);
onFolderChanged(mInbox);
} else {
// TODO: revisit if this block is necessary
// Set the correct context for what the conversation view will be now.
onFolderChanged(mInbox);
}
mConversationListVisible = true;
onConversationVisibilityChanged(false);
onConversationListVisibilityChanged(true);
}
private void safelyPopBackStack(boolean inLoaderCallbacks) {
safelyPopBackStack(-1, inLoaderCallbacks);
}
/**
* Pop to a specified point in the fragment back stack without causing IllegalStateExceptions
* from committing a fragment transaction "at the wrong time".
* <p>
* If the caller specifies that we are in
* the scope of an {@link LoaderCallbacks#onLoadFinished(android.content.Loader, Object)},
* this method will pop back in a Handler. The deferred job will also check that the Activity
* is in a valid state for fragment transactions, using {@link #safeToModifyFragments()}.
* Otherwise, this method will pop back immediately if safe. Finally, if we are not in
* onLoadFinished and it's not safe, this method will just ignore the request.
*
* @param transactionId back stack destination to pop to, or -1 to just pop the top
* @param inLoaderCallbacks whether we are in the scope of an onLoadFinished (when fragment
* transactions are disallowed)
*/
private void safelyPopBackStack(int transactionId, boolean inLoaderCallbacks) {
final PopBackStackRunnable r = new PopBackStackRunnable(transactionId);
if (inLoaderCallbacks) {
// always run deferred. ensure deferred job checks safety.
mHandler.post(r);
} else if (safeToModifyFragments()) {
// run now
r.popBackStack();
} else {
// ignore
LogUtils.i(LOG_TAG, "Activity has been saved; ignoring unsafe immediate request"
+ " to pop back stack");
}
}
@Override
public boolean shouldShowFirstConversation() {
return false;
}
@Override
public void onUndoAvailable(ToastBarOperation op) {
if (op != null && mAccount.supportsCapability(UIProvider.AccountCapabilities.UNDO)) {
final int mode = mViewMode.getMode();
final ConversationListFragment convList = getConversationListFragment();
switch (mode) {
case ViewMode.SEARCH_RESULTS_CONVERSATION:
case ViewMode.CONVERSATION:
mToastBar.setConversationMode(true);
mToastBar.show(
getUndoClickedListener(
convList != null ? convList.getAnimatedAdapter() : null),
0,
Utils.convertHtmlToPlainText
(op.getDescription(mActivity.getActivityContext())),
true, /* showActionIcon */
R.string.undo,
true, /* replaceVisibleToast */
op);
break;
case ViewMode.SEARCH_RESULTS_LIST:
case ViewMode.CONVERSATION_LIST:
if (convList != null) {
mToastBar.setConversationMode(false);
mToastBar.show(
getUndoClickedListener(convList.getAnimatedAdapter()),
0,
Utils.convertHtmlToPlainText
(op.getDescription(mActivity.getActivityContext())),
true, /* showActionIcon */
R.string.undo,
true, /* replaceVisibleToast */
op);
} else {
mActivity.setPendingToastOperation(op);
}
break;
}
}
}
@Override
protected void hideOrRepositionToastBar(boolean animated) {
mToastBar.hide(animated);
}
@Override
public void onError(final Folder folder, boolean replaceVisibleToast) {
final int mode = mViewMode.getMode();
switch (mode) {
case ViewMode.SEARCH_RESULTS_LIST:
case ViewMode.CONVERSATION_LIST:
showErrorToast(folder, replaceVisibleToast);
break;
default:
break;
}
}
@Override
public String getHelpContext() {
final int mode = mViewMode.getMode();
switch (mode) {
case ViewMode.FOLDER_LIST:
return mContext.getString(R.string.one_pane_folder_list_help_context);
}
return super.getHelpContext();
}
private final class PopBackStackRunnable implements Runnable {
private final int mTransactionId;
public PopBackStackRunnable(int transactionId) {
mTransactionId = transactionId;
}
public void popBackStack() {
final FragmentManager fm = mActivity.getFragmentManager();
if (mTransactionId < 0) {
fm.popBackStackImmediate();
} else {
fm.popBackStackImmediate(mTransactionId, 0);
}
}
@Override
public void run() {
if (safeToModifyFragments()) {
popBackStack();
} else {
LogUtils.i(LOG_TAG, "Activity has been saved; ignoring unsafe deferred request"
+ " to pop back stack");
}
}
}
/**
* The default behavior calls mDrawerObserver's notifyChanged(). So, to notify the consumer of
* the observer that the drawer is closed, we simply make a call to
* {@link AbstractActivityController#closeDrawerForNewList()}.
*/
public void notifyDrawerClosed() {
super.closeDrawerForNewList();
}
/**
* If the drawer is open, the function locks the drawer to the closed, thereby sliding in
* the drawer to the left edge, disabling events, and refreshing it once it's either closed
* or put in an idle state.
*/
@Override
public void closeDrawerForNewList() {
final ConversationListFragment conversationList = getConversationListFragment();
if (conversationList != null) {
mListViewForAnimating = conversationList.getListView();
} else {
// There is no conversation list to animate, so just set it to null
mListViewForAnimating = null;
}
if (mDrawerContainer.isDrawerOpen(mDrawerPullout)) {
// Lets the drawer listener update the drawer contents and notify the FolderListFragment
mHasNewAccountOrFolder = true;
mDrawerContainer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
} else {
// Drawer is already closed, notify observers that is the case.
notifyDrawerClosed();
}
}
@Override
public boolean isDrawerEnabled() {
// The drawer is enabled for one pane mode
return true;
}
}