Merge "Import translations. DO NOT MERGE" into jb-ub-mail
diff --git a/res/drawable-sw600dp/folder_item.xml b/res/drawable-sw600dp/folder_item.xml
new file mode 100644
index 0000000..7632b33
--- /dev/null
+++ b/res/drawable-sw600dp/folder_item.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2011 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/list_pressed_holo" />
+    <item android:state_activated="true" android:drawable="@drawable/list_activated_holo" />
+    <item android:drawable="@android:color/transparent" />
+</selector>
diff --git a/res/drawable/folder_item.xml b/res/drawable/folder_item.xml
index 7632b33..f047291 100644
--- a/res/drawable/folder_item.xml
+++ b/res/drawable/folder_item.xml
@@ -16,8 +16,8 @@
      limitations under the License.
 -->
 
+<!-- On phone we don't want to have an activated state. -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_pressed="true" android:drawable="@drawable/list_pressed_holo" />
-    <item android:state_activated="true" android:drawable="@drawable/list_activated_holo" />
     <item android:drawable="@android:color/transparent" />
 </selector>
diff --git a/res/layout/folder_list.xml b/res/layout/folder_list.xml
index 28ea922..19b0e36 100644
--- a/res/layout/folder_list.xml
+++ b/res/layout/folder_list.xml
@@ -16,7 +16,7 @@
      limitations under the License.
 -->
 
-<FrameLayout  xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.mail.ui.FolderListLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
@@ -47,4 +47,4 @@
             android:textAppearance="?android:attr/textAppearanceMedium"/>
     </RelativeLayout>
 
-</FrameLayout>
+</com.android.mail.ui.FolderListLayout>
diff --git a/res/layout/one_pane_activity.xml b/res/layout/one_pane_activity.xml
index a4b31d6..07974b7 100644
--- a/res/layout/one_pane_activity.xml
+++ b/res/layout/one_pane_activity.xml
@@ -15,7 +15,8 @@
      limitations under the License.
 -->
 
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.mail.ui.OnePaneRoot xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/one_pane_root"
     android:layout_width="match_parent"
     android:layout_height="match_parent" >
 
@@ -30,4 +31,4 @@
         android:id="@+id/toast_bar"
         style="@style/ToastBarStyle" />
 
-</FrameLayout>
\ No newline at end of file
+</com.android.mail.ui.OnePaneRoot>
\ No newline at end of file
diff --git a/src/com/android/mail/browse/ConversationContainer.java b/src/com/android/mail/browse/ConversationContainer.java
index 5fa3b0b..bbee924 100644
--- a/src/com/android/mail/browse/ConversationContainer.java
+++ b/src/com/android/mail/browse/ConversationContainer.java
@@ -37,6 +37,7 @@
 import com.android.mail.utils.DequeMap;
 import com.android.mail.utils.InputSmoother;
 import com.android.mail.utils.LogUtils;
+import com.android.mail.utils.Utils;
 import com.google.common.collect.Lists;
 
 import java.util.List;
@@ -565,6 +566,12 @@
     }
 
     @Override
+    public void requestLayout() {
+        Utils.checkRequestLayout(this);
+        super.requestLayout();
+    }
+
+    @Override
     protected void dispatchDraw(Canvas canvas) {
         super.dispatchDraw(canvas);
 
diff --git a/src/com/android/mail/providers/MailAppProvider.java b/src/com/android/mail/providers/MailAppProvider.java
index 158287c..fbc2917 100644
--- a/src/com/android/mail/providers/MailAppProvider.java
+++ b/src/com/android/mail/providers/MailAppProvider.java
@@ -39,9 +39,12 @@
 import com.android.mail.utils.LogUtils;
 import com.android.mail.utils.MatrixCursorWithExtra;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 
+import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.regex.Pattern;
@@ -168,11 +171,11 @@
         extras.putInt(AccountCursorExtraKeys.ACCOUNTS_LOADED, mAccountsFullyLoaded ? 1 : 0);
 
         // Make a copy of the account cache
-
-        final Set<AccountCacheEntry> accountList;
+        final List<AccountCacheEntry> accountList = Lists.newArrayList();
         synchronized (mAccountCache) {
-            accountList = ImmutableSet.copyOf(mAccountCache.values());
+            accountList.addAll(mAccountCache.values());
         }
+        Collections.sort(accountList);
 
         final MatrixCursor cursor =
                 new MatrixCursorWithExtra(resultProjection, accountList.size(), extras);
@@ -366,10 +369,16 @@
     }
 
     private void addAccountImpl(Account account, Uri accountsQueryUri, boolean notify) {
+        addAccountImpl(account, accountsQueryUri, mAccountCache.size(), notify);
+    }
+
+    private void addAccountImpl(Account account, Uri accountsQueryUri, int position,
+            boolean notify) {
         synchronized (mAccountCache) {
             if (account != null) {
                 LogUtils.v(LOG_TAG, "adding account %s", account);
-                mAccountCache.put(account.uri, new AccountCacheEntry(account, accountsQueryUri));
+                mAccountCache.put(account.uri,
+                        new AccountCacheEntry(account, accountsQueryUri, position));
             }
         }
         // Explicitly calling this out of the synchronized block in case any of the observers get
@@ -447,16 +456,20 @@
         final Set<String> accountsStringSet = preference.getStringSet(ACCOUNT_LIST_KEY, null);
 
         if (accountsStringSet != null) {
+            int pos = 0;
             for (String serializedAccount : accountsStringSet) {
                 try {
+                    // TODO (pwestbro): we are creating duplicate AccountCacheEntry objects.
+                    // One here, and one in addAccountImpl.  We should stop doing that.
                     final AccountCacheEntry accountEntry =
-                            new AccountCacheEntry(serializedAccount);
+                            new AccountCacheEntry(serializedAccount, pos);
                     if (accountEntry.mAccount.settings != null) {
-                        addAccountImpl(accountEntry.mAccount, accountEntry.mAccountsQueryUri,
+                        addAccountImpl(accountEntry.mAccount, accountEntry.mAccountsQueryUri, pos,
                                 false /* don't notify */);
                     } else {
                         LogUtils.e(LOG_TAG, "Dropping account that doesn't specify settings");
                     }
+                    pos++;
                 } catch (Exception e) {
                     // Unable to create account object, skip to next
                     LogUtils.e(LOG_TAG, e,
@@ -469,10 +482,11 @@
     }
 
     private void cacheAccountList() {
-        final Set<AccountCacheEntry> accountList;
+        final List<AccountCacheEntry> accountList = Lists.newArrayList();
         synchronized (mAccountCache) {
-            accountList = ImmutableSet.copyOf(mAccountCache.values());
+            accountList.addAll(mAccountCache.values());
         }
+        Collections.sort(accountList);
 
         final Set<String> serializedAccounts = Sets.newHashSet();
         for (AccountCacheEntry accountEntry : accountList) {
@@ -521,11 +535,15 @@
             accountList = ImmutableSet.copyOf(mAccountCache.values());
         }
 
+        int lastPosition = 0;
         // Build a set of the account uris that had been associated with that query
-        final Set<Uri> previousQueryUriMap = Sets.newHashSet();
+        final Set<Uri> previousQueryUriSet = Sets.newHashSet();
         for (AccountCacheEntry entry : accountList) {
             if (accountsQueryUri.equals(entry.mAccountsQueryUri)) {
-                previousQueryUriMap.add(entry.mAccount.uri);
+                previousQueryUriSet.add(entry.mAccount.uri);
+            }
+            if (entry.mPosition > lastPosition) {
+                lastPosition = entry.mPosition;
             }
         }
 
@@ -536,19 +554,23 @@
         mAccountsFullyLoaded = extra.getInt(AccountCursorExtraKeys.ACCOUNTS_LOADED) != 0;
 
         final Set<Uri> newQueryUriMap = Sets.newHashSet();
+
+        // We are relying on the fact that all accounts are added in the order specified in the
+        // cursor.  Initially assume that we insert these items to at the end of the list
+        int pos = lastPosition;
         while (data.moveToNext()) {
             final Account account = new Account(data);
             final Uri accountUri = account.uri;
             newQueryUriMap.add(accountUri);
-            addAccountImpl(account, accountsQueryUri, false /* don't notify */);
+            addAccountImpl(account, accountsQueryUri, pos++, false /* don't notify */);
         }
         // Remove all of the accounts that are in the new result set
-        previousQueryUriMap.removeAll(newQueryUriMap);
+        previousQueryUriSet.removeAll(newQueryUriMap);
 
         // For all of the entries that had been in the previous result set, and are not
         // in the new result set, remove them from the cache
-        if (previousQueryUriMap.size() > 0 && mAccountsFullyLoaded) {
-            removeAccounts(previousQueryUriMap, false /* don't notify */);
+        if (previousQueryUriSet.size() > 0 && mAccountsFullyLoaded) {
+            removeAccounts(previousQueryUriSet, false /* don't notify */);
         }
         broadcastAccountChange();
     }
@@ -557,9 +579,10 @@
      * Object that allows the Account Cache provider to associate the account with the content
      * provider uri that originated that account.
      */
-    private static class AccountCacheEntry {
+    private static class AccountCacheEntry implements Comparable<AccountCacheEntry> {
         final Account mAccount;
         final Uri mAccountsQueryUri;
+        final int mPosition;
 
         private static final String ACCOUNT_ENTRY_COMPONENT_SEPARATOR = "^**^";
         private static final Pattern ACCOUNT_ENTRY_COMPONENT_SEPARATOR_PATTERN =
@@ -567,9 +590,10 @@
 
         private static final int NUMBER_MEMBERS = 2;
 
-        public AccountCacheEntry(Account account, Uri accountQueryUri) {
+        public AccountCacheEntry(Account account, Uri accountQueryUri, int position) {
             mAccount = account;
             mAccountsQueryUri = accountQueryUri;
+            mPosition = position;
         }
 
         /**
@@ -591,7 +615,8 @@
          * ignoring the newly created object if the exception is thrown.
          * @param serializedString
          */
-        public AccountCacheEntry(String serializedString) throws IllegalArgumentException {
+        public AccountCacheEntry(String serializedString, int position)
+                throws IllegalArgumentException {
             String[] cacheEntryMembers = TextUtils.split(serializedString,
                     ACCOUNT_ENTRY_COMPONENT_SEPARATOR_PATTERN);
             if (cacheEntryMembers.length != NUMBER_MEMBERS) {
@@ -611,6 +636,12 @@
             }
             mAccountsQueryUri = !TextUtils.isEmpty(cacheEntryMembers[1]) ?
                     Uri.parse(cacheEntryMembers[1]) : null;
+            mPosition = position;
+        }
+
+        @Override
+        public int compareTo(AccountCacheEntry o) {
+            return o.mPosition - mPosition;
         }
     }
 }
diff --git a/src/com/android/mail/ui/AbstractActivityController.java b/src/com/android/mail/ui/AbstractActivityController.java
index 9211f80..daf701a 100644
--- a/src/com/android/mail/ui/AbstractActivityController.java
+++ b/src/com/android/mail/ui/AbstractActivityController.java
@@ -479,37 +479,56 @@
 
     @Override
     public void onFolderChanged(Folder folder) {
-        if (!Objects.equal(mFolder, folder)) {
-            commitDestructiveActions(false);
-        }
         changeFolder(folder, null);
     }
 
     /**
+     * Sets the folder state without changing view mode and without creating a list fragment, if
+     * possible.
+     * @param folder
+     */
+    private void setListContext(Folder folder, String query) {
+        updateFolder(folder);
+        if (query != null) {
+            mConvListContext = ConversationListContext.forSearchQuery(mAccount, mFolder, query);
+        } else {
+            mConvListContext = ConversationListContext.forFolder(mAccount, mFolder);
+        }
+        // Add the folder that we were viewing to the recent folders list.
+        // TODO: this may need to be fine tuned.  If this is the signal that is indicating that
+        // the list is shown to the user, this could fire in one pane if the user goes directly
+        // to a conversation
+        updateRecentFolderList();
+        cancelRefreshTask();
+    }
+
+    /**
      * Changes the folder to the value provided here. This causes the view mode to change.
      * @param folder the folder to change to
      * @param query if non-null, this represents the search string that the folder represents.
      */
     private void changeFolder(Folder folder, String query) {
+        if (!Objects.equal(mFolder, folder)) {
+            commitDestructiveActions(false);
+        }
         if (folder != null && !folder.equals(mFolder)
                 || (mViewMode.getMode() != ViewMode.CONVERSATION_LIST)) {
-            updateFolder(folder);
-            if (query != null) {
-                mConvListContext = ConversationListContext.forSearchQuery(mAccount, mFolder, query);
-            } else {
-                mConvListContext = ConversationListContext.forFolder(mAccount, mFolder);
-            }
+            setListContext(folder, query);
             showConversationList(mConvListContext);
-
-            // Add the folder that we were viewing to the recent folders list.
-            // TODO: this may need to be fine tuned.  If this is the signal that is indicating that
-            // the list is shown to the user, this could fire in one pane if the user goes directly
-            // to a conversation
-            updateRecentFolderList();
-            cancelRefreshTask();
         }
     }
 
+    /**
+     * Update the conversation list to {@link #mConvListContext} without creating a new frament if
+     * possible.
+     */
+    protected abstract void updateConversationList();
+
+    private void setFirstFolder(Folder folder, String query) {
+        setListContext(folder, query);
+        updateConversationList();
+    }
+
     @Override
     public void onFolderSelected(Folder folder) {
         onFolderChanged(folder);
@@ -660,11 +679,8 @@
             }
             if (savedState.containsKey(SAVED_FOLDER)) {
                 final Folder folder = (Folder) savedState.getParcelable(SAVED_FOLDER);
-                if (savedState.containsKey(SAVED_QUERY)) {
-                    changeFolder(folder, savedState.getString(SAVED_QUERY));
-                } else {
-                    onFolderChanged(folder);
-                }
+                final String query = savedState.getString(SAVED_QUERY, null);
+                setFirstFolder(folder, query);
             }
         } else if (intent != null) {
             handleIntent(intent);
diff --git a/src/com/android/mail/ui/ConversationListFragment.java b/src/com/android/mail/ui/ConversationListFragment.java
index 746ce02..530eec7 100644
--- a/src/com/android/mail/ui/ConversationListFragment.java
+++ b/src/com/android/mail/ui/ConversationListFragment.java
@@ -153,6 +153,8 @@
 
     @Override
     public void onResume() {
+        Utils.dumpLayoutRequests("CLF.onResume()", getView());
+
         super.onResume();
         // Hacky workaround for http://b/6946182
         Utils.fixSubTreeLayoutIfOrphaned(getView(), "ConversationListFragment");
@@ -345,11 +347,20 @@
         if (conversationListCursor != null && conversationListCursor.isRefreshReady()) {
             conversationListCursor.sync();
         }
+        Utils.dumpLayoutRequests("CLF.onCreateView()", container);
         return rootView;
     }
 
     @Override
+    public void onDestroy() {
+        Utils.dumpLayoutRequests("CLF.onDestroy()", getView());
+        super.onDestroy();
+    }
+
+    @Override
     public void onDestroyView() {
+        Utils.dumpLayoutRequests("CLF.onDestroyView()", getView());
+
         // If this fragment is being retained, onSaveInstance will not be called, so we need to
         // manage saving the state ourselves.  Unfortunately we don't have a signal indicates that
         // this fragment instance will be reused, so we have to save the state in all cases.
@@ -437,6 +448,7 @@
 
     @Override
     public void onPause() {
+        Utils.dumpLayoutRequests("CLF.onPause()", getView());
         super.onPause();
     }
 
@@ -450,12 +462,14 @@
 
     @Override
     public void onStart() {
+        Utils.dumpLayoutRequests("CLF.onStart()", getView());
         super.onStart();
         mHandler.postDelayed(mUpdateTimestampsRunnable, TIMESTAMP_UPDATE_INTERVAL);
     }
 
     @Override
     public void onStop() {
+        Utils.dumpLayoutRequests("CLF.onStop()", getView());
         super.onStop();
         mHandler.removeCallbacks(mUpdateTimestampsRunnable);
     }
diff --git a/src/com/android/mail/ui/FolderListFragment.java b/src/com/android/mail/ui/FolderListFragment.java
index 71bcb98..4cec4f4 100644
--- a/src/com/android/mail/ui/FolderListFragment.java
+++ b/src/com/android/mail/ui/FolderListFragment.java
@@ -92,6 +92,8 @@
 
     @Override
     public void onResume() {
+        Utils.dumpLayoutRequests("FLF(" + this + ").onResume()", getView());
+
         super.onResume();
         // Hacky workaround for http://b/6946182
         Utils.fixSubTreeLayoutIfOrphaned(getView(), "FolderListFragment");
@@ -165,12 +167,32 @@
         if (mParentFolder != null) {
             mSelectedFolder = mParentFolder;
         }
+        Utils.dumpLayoutRequests("FLF(" + this + ").onCreateView()", rootView);
 
         return rootView;
     }
 
     @Override
+    public void onStart() {
+        Utils.dumpLayoutRequests("FLF(" + this + ").onStart()", getView());
+        super.onStart();
+    }
+
+    @Override
+    public void onStop() {
+        Utils.dumpLayoutRequests("FLF(" + this + ").onStop()", getView());
+        super.onStop();
+    }
+
+    @Override
+    public void onPause() {
+        Utils.dumpLayoutRequests("FLF(" + this + ").onPause()", getView());
+        super.onPause();
+    }
+
+    @Override
     public void onDestroyView() {
+        Utils.dumpLayoutRequests("FLF(" + this + ").onDestoryView()", getView());
         // Clear the adapter.
         setListAdapter(null);
         if (mFolderObserver != null) {
diff --git a/src/com/android/mail/ui/FolderListLayout.java b/src/com/android/mail/ui/FolderListLayout.java
new file mode 100644
index 0000000..0095df9
--- /dev/null
+++ b/src/com/android/mail/ui/FolderListLayout.java
@@ -0,0 +1,41 @@
+package com.android.mail.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+import com.android.mail.utils.LogUtils;
+import com.android.mail.utils.Utils;
+
+/**
+ * temporary annonated FrameLayout to help find cases of b/6946182
+ */
+public class FolderListLayout extends FrameLayout {
+
+    public FolderListLayout(Context c) {
+        this(c, null);
+    }
+
+    public FolderListLayout(Context c, AttributeSet attrs) {
+        super(c, attrs);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        LogUtils.w(Utils.VIEW_DEBUGGING_TAG, "FolderListLayout(%s).onMeasure() called", this);
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        LogUtils.w(Utils.VIEW_DEBUGGING_TAG, "FolderListLayout(%s).onLayout() called", this);
+        super.onLayout(changed, left, top, right, bottom);
+    }
+
+    @Override
+    public void requestLayout() {
+        Utils.checkRequestLayout(this);
+        super.requestLayout();
+    }
+
+}
diff --git a/src/com/android/mail/ui/OnePaneController.java b/src/com/android/mail/ui/OnePaneController.java
index 75b3575..663aac5 100644
--- a/src/com/android/mail/ui/OnePaneController.java
+++ b/src/com/android/mail/ui/OnePaneController.java
@@ -406,6 +406,50 @@
         }
     }
 
+    /**
+     * Update the conversation list without creating another fragment, if possible
+     */
+    @Override
+    protected void updateConversationList(){
+        enableCabMode();
+        // TODO(viki): Check if the account has been changed since the previous
+        // time.
+        if (ConversationListContext.isSearchResult(mConvListContext)) {
+            mViewMode.enterSearchResultsListMode();
+        } else {
+            mViewMode.enterConversationListMode();
+        }
+        // TODO(viki): This account transition looks strange in two pane mode.
+        // Revisit as the app is coming together and improve the look and feel.
+        final int transition = mConversationListNeverShown
+                ? FragmentTransaction.TRANSIT_FRAGMENT_FADE
+                : FragmentTransaction.TRANSIT_FRAGMENT_OPEN;
+        Fragment listFragment = getConversationListFragment();
+        if (listFragment == null) {
+            listFragment = ConversationListFragment.newInstance(mConvListContext);
+            if (!inInbox(mAccount, mConvListContext)) {
+                // Maintain fragment transaction history so we can get back to the
+                // fragment used to launch this list.
+                mLastConversationListTransactionId = replaceFragment(listFragment,
+                        transition, TAG_CONVERSATION_LIST);
+            } else {
+                // If going to the inbox, clear the folder list transaction history.
+                mInbox = mConvListContext.folder;
+                mLastInboxConversationListTransactionId = replaceFragment(listFragment,
+                        transition, TAG_CONVERSATION_LIST);
+                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;
+            }
+        }
+        mConversationListVisible = true;
+        onConversationVisibilityChanged(false);
+        onConversationListVisibilityChanged(true);
+        mConversationListNeverShown = false;
+    }
+
     private boolean isTransactionIdValid(int id) {
         return id >= 0;
     }
diff --git a/src/com/android/mail/ui/OnePaneRoot.java b/src/com/android/mail/ui/OnePaneRoot.java
new file mode 100644
index 0000000..8ffaede
--- /dev/null
+++ b/src/com/android/mail/ui/OnePaneRoot.java
@@ -0,0 +1,45 @@
+package com.android.mail.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+import com.android.mail.utils.LogTag;
+import com.android.mail.utils.LogUtils;
+import com.android.mail.utils.Utils;
+
+/**
+ * TODO: Insert description here. (generated by ath)
+ */
+public class OnePaneRoot extends FrameLayout {
+
+    private static final String LOG_TAG = LogTag.getLogTag();
+
+    public OnePaneRoot(Context c) {
+        this(c, null);
+    }
+
+    public OnePaneRoot(Context c, AttributeSet attrs) {
+        super(c, attrs);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        LogUtils.w(Utils.VIEW_DEBUGGING_TAG, "OnePaneLayout(%s).onMeasure() called", this);
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        LogUtils.w(Utils.VIEW_DEBUGGING_TAG, "OnePaneLayout(%s).onLayout() START", this);
+        super.onLayout(changed, left, top, right, bottom);
+        LogUtils.w(Utils.VIEW_DEBUGGING_TAG, "OnePaneLayout(%s).onLayout() FINISH", this);
+    }
+
+    @Override
+    public void requestLayout() {
+        Utils.checkRequestLayout(this);
+        super.requestLayout();
+    }
+
+}
diff --git a/src/com/android/mail/ui/SwipeableListView.java b/src/com/android/mail/ui/SwipeableListView.java
index 745cd37..b9921e1 100644
--- a/src/com/android/mail/ui/SwipeableListView.java
+++ b/src/com/android/mail/ui/SwipeableListView.java
@@ -19,6 +19,7 @@
 
 import android.content.Context;
 import android.content.res.Configuration;
+import android.graphics.Rect;
 import android.net.Uri;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
@@ -37,6 +38,7 @@
 import com.android.mail.ui.SwipeHelper.Callback;
 import com.android.mail.utils.LogTag;
 import com.android.mail.utils.LogUtils;
+import com.android.mail.utils.Utils;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -78,6 +80,23 @@
         mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
     }
 
+    @Override
+    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
+        LogUtils.w(Utils.VIEW_DEBUGGING_TAG,
+                "START CLF-ListView.onFocusChanged layoutRequested=%s root.layoutRequested=%s",
+                isLayoutRequested(), getRootView().isLayoutRequested());
+        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+        LogUtils.w(Utils.VIEW_DEBUGGING_TAG, new Error(),
+                "FINISH CLF-ListView.onFocusChanged layoutRequested=%s root.layoutRequested=%s",
+                isLayoutRequested(), getRootView().isLayoutRequested());
+    }
+
+    @Override
+    public void requestLayout() {
+        Utils.checkRequestLayout(this);
+        super.requestLayout();
+    }
+
     /**
      * Enable swipe gestures.
      */
diff --git a/src/com/android/mail/ui/TwoPaneController.java b/src/com/android/mail/ui/TwoPaneController.java
index 953c2a5..b12bad6 100644
--- a/src/com/android/mail/ui/TwoPaneController.java
+++ b/src/com/android/mail/ui/TwoPaneController.java
@@ -121,6 +121,21 @@
         initializeConversationListFragment(true);
     }
 
+    /**
+     * Update the conversation list without creating another fragment, if possible
+     */
+    @Override
+    protected void updateConversationList(){
+        exitCabMode();
+        FolderListFragment folderList = getFolderListFragment();
+        if (folderList == null && mViewMode.getMode() == ViewMode.CONVERSATION_LIST) {
+            // Create a folder list fragment if none exists.
+            renderFolderList();
+            folderList = getFolderListFragment();
+        }
+        initializeConversationListFragment(true);
+    }
+
     @Override
     public void showFolderList() {
         // On two-pane layouts, showing the folder list takes you to the top level of the
diff --git a/src/com/android/mail/ui/TwoPaneLayout.java b/src/com/android/mail/ui/TwoPaneLayout.java
index bee4bb9..b703ef1 100644
--- a/src/com/android/mail/ui/TwoPaneLayout.java
+++ b/src/com/android/mail/ui/TwoPaneLayout.java
@@ -748,4 +748,22 @@
         mOutstandingAnimator = animator;
         animator.start();
     }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        LogUtils.w(Utils.VIEW_DEBUGGING_TAG, "TPL(%s).onMeasure()", this);
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        LogUtils.w(Utils.VIEW_DEBUGGING_TAG, "TPL(%s).onLayout()", this);
+        super.onLayout(changed, l, t, r, b);
+    }
+
+    @Override
+    public void requestLayout() {
+        Utils.checkRequestLayout(this);
+        super.requestLayout();
+    }
 }
diff --git a/src/com/android/mail/utils/Utils.java b/src/com/android/mail/utils/Utils.java
index c262987..366432e 100644
--- a/src/com/android/mail/utils/Utils.java
+++ b/src/com/android/mail/utils/Utils.java
@@ -87,6 +87,10 @@
     public static final String EXTRA_COMPOSE_URI = "composeUri";
     public static final String EXTRA_CONVERSATION = "conversationUri";
     public static final String EXTRA_FOLDER = "folder";
+
+    /** Extra tag for debugging the blank fragment problem. */
+    public static final String VIEW_DEBUGGING_TAG = "MailBlankFragment";
+
     /*
      * Notifies that changes happened. Certain UI components, e.g., widgets, can
      * register for this {@link Intent} and update accordingly. However, this
@@ -1049,7 +1053,11 @@
      * Hacky method to allow invalidating views all the way up the hierarchy.
      */
     public static void markDirtyTillRoot(String message, View v) {
-        LogUtils.d(LOG_TAG, "%s: markingDirtyTillRoot", message);
+        // During development, we want to log extra debugging information, and disable the
+        // hacky workaround to help diagnose the underlying problem.
+        if (LogUtils.isDebugLoggingEnabled(VIEW_DEBUGGING_TAG)) return;
+
+        LogUtils.d(VIEW_DEBUGGING_TAG, "%s: markingDirtyTillRoot", message);
         v.invalidate();
         ViewParent parent = v.getParent();
         while (parent != null) {
@@ -1058,4 +1066,56 @@
         }
     }
 
+    public static void checkRequestLayout(View v) {
+        boolean inLayout = false;
+        final View root = v.getRootView();
+
+        if (root == null) {
+            return;
+        }
+
+        final Error e = new Error();
+        for (StackTraceElement ste : e.getStackTrace()) {
+            if ("android.view.ViewGroup".equals(ste.getClassName())
+                    && "layout".equals(ste.getMethodName())) {
+                inLayout = true;
+                break;
+            }
+        }
+        if (inLayout && !v.isLayoutRequested()) {
+            LogUtils.e(VIEW_DEBUGGING_TAG,
+                    e, "WARNING: in requestLayout during layout pass, view=%s", v);
+        }
+    }
+
+    /**
+     * Logs extra information about the views to help find the problem with blank fragments.
+     * To turn on this debugging, enable the "MailBlankFragment" tag with
+     * adb shell setprop log.tag.MailBlankFragment VERBOSE
+     * @param message
+     * @param v
+     */
+    public static void dumpLayoutRequests(String message, View v) {
+        LogUtils.w(VIEW_DEBUGGING_TAG, "dumpLayoutRequests: %s", message);
+
+        while (v != null) {
+            LogUtils.w(VIEW_DEBUGGING_TAG,
+                    "view item: %s mw/mh=%d/%d w/h=%d/%d layoutRequested=%s vis=%s id=0x%x",
+                    v, v.getMeasuredWidth(), v.getMeasuredHeight(), v.getWidth(), v.getHeight(),
+                    v.isLayoutRequested(), v.getVisibility(), v.getId());
+
+            ViewParent vp = v.getParent();
+            if (vp instanceof ViewGroup) {
+                v = (ViewGroup) vp;
+            } else {
+                if (vp != null) {
+                    // this is the root. can't really get access to this guy
+                    LogUtils.w(VIEW_DEBUGGING_TAG,
+                            "view item: (ViewRootImpl) isLayoutRequested=%s\n",
+                            vp.isLayoutRequested());
+                }
+                v = null;
+            }
+        }
+    }
 }