Merge "Revert "Exit cab mode when we change folders"" into jb-ub-mail-ur10
diff --git a/res/layout/conversation_message_details_header_expanded.xml b/res/layout/conversation_message_details_header_expanded.xml
index af2bcbf..f63cac6 100644
--- a/res/layout/conversation_message_details_header_expanded.xml
+++ b/res/layout/conversation_message_details_header_expanded.xml
@@ -26,7 +26,7 @@
     android:paddingLeft="@dimen/message_details_header_padding_start_collapsed"
     android:paddingRight="@dimen/message_details_header_padding_end"
     app:columnCount="3"
-    app:rowCount="5" >
+    app:rowCount="6" >
 
     <TextView
         android:id="@+id/from_heading"
@@ -143,5 +143,26 @@
         app:layout_row="4"
         android:visibility="gone"
         style="@style/MessageDetailsValueStyle" />
+    <TextView
+        android:id="@+id/date_heading"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/message_header_inner_side_padding"
+        android:layout_marginRight="@dimen/message_header_inner_side_padding"
+        app:layout_column="0"
+        app:layout_row="5"
+        android:text="@string/date_heading"
+        android:visibility="gone"
+        style="@style/MessageHeaderSmallStyle" />
+    <TextView
+        android:id="@+id/date_details"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        app:layout_gravity="fill_horizontal"
+        android:layout_marginBottom="4dp"
+        app:layout_column="1"
+        app:layout_row="5"
+        android:visibility="gone"
+        style="@style/MessageDetailsValueStyle" />
 
 </android.support.v7.widget.GridLayout>
diff --git a/res/layout/secure_conversation_view.xml b/res/layout/secure_conversation_view.xml
index d21ac4b..2e391eb 100644
--- a/res/layout/secure_conversation_view.xml
+++ b/res/layout/secure_conversation_view.xml
@@ -75,7 +75,8 @@
                 <com.android.mail.browse.MessageWebView
                     android:id="@+id/webview"
                     android:layout_width="match_parent"
-                    android:layout_height="wrap_content" />
+                    android:layout_height="wrap_content"
+                    android:scrollbars="none"/>
                 <View
                     android:layout_width="1dp"
                     android:layout_height="match_parent"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d350098..d4106d6 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -440,9 +440,9 @@
           viewing it. [CHAR LIMIT=40] -->
     <string name="add_label">Add folder</string>
 
-    <!-- New Message notification text that appears over conversation view on incoming message. [CHAR LIMIT=30] -->
+    <!-- New Message notification text that appears over conversation view on incoming message. [CHAR LIMIT=40] -->
     <string name="new_incoming_messages_one">New message from <xliff:g id="sender">%s</xliff:g>. Show.</string>
-    <!-- New Message notification text that appears over conversation view on incoming messages. Will only be used if there is more than one new message. [CHAR LIMIT=30] -->
+    <!-- New Message notification text that appears over conversation view on incoming messages. Will only be used if there is more than one new message. [CHAR LIMIT=40] -->
     <plurals name="new_incoming_messages_many">
         <item quantity="other"><xliff:g id="count">%1$d</xliff:g> new messages. Show.</item>
     </plurals>
@@ -461,18 +461,18 @@
     <plurals name="show_messages_read">
         <item quantity="other"><xliff:g id="count" example="4">%1$d</xliff:g> older messages</item>
     </plurals>
-
+    <!-- Shown to display the from address of the message [CHAR LIMIT=10] -->
     <string name="from_heading">From:\u0020</string>
     <!-- Shown to display the reply to address of the message [CHAR LIMIT=20] -->
     <string name="replyto_heading">Reply-to:\u0020</string>
-    <!-- Shown to display the recipient(s) of the message [CHAR LIMIT=10] -->
-    <!-- Shown in HTML to display the recipient(s) of the message [CHAR LIMIT=10] -->
+    <!-- Shown to display the to recipient(s) of the message [CHAR LIMIT=10] -->
     <string name="to_heading">To:\u0020</string>
-    <!-- Shown in HTML to display the recipient(s) of the message [CHAR LIMIT=10] -->
+    <!-- Shown to display the cc recipient(s) of the message [CHAR LIMIT=10] -->
     <string name="cc_heading">Cc:\u0020</string>
-    <!-- Shown in HTML to display the recipient(s) of the message [CHAR LIMIT=10] -->
+    <!-- Shown to display the bcc recipient(s) of the message [CHAR LIMIT=10] -->
     <string name="bcc_heading">Bcc:\u0020</string>
-    <!-- Shown to display the from address of the message [CHAR LIMIT=10] -->
+    <!-- Shown to display the recipient(s) of the message [CHAR LIMIT=10] -->
+    <string name="date_heading">Date:\u0020</string>
     <!-- Displayed above an HTML message to show the images in that message [CHAR LIMIT=40] -->
     <string name="show_images">Show pictures</string>
     <!-- Displayed above an HTML message to always show images in messages from that sender [CHAR LIMIT=40] -->
@@ -901,11 +901,6 @@
         receiving any new mail. [CHAR LIMIT=250] -->
     <string name="account_sync_off">Account sync is off.</string>
 
-    <!-- Tip for letting user know that airplane mode is enabled on
-        their device, in case they are wondering why they are not
-        receiving any new mail. [CHAR LIMIT=250] -->
-    <string name="airplane_mode_on">Airplane mode is on</string>
-
     <!-- Hint text for user to enable sync in Gmail's account settings.
          The whole string should read "Turn on in Account settings.", but because we need
          "Account settings" to appear as blue link text, it's a parameter here.
diff --git a/src/com/android/mail/browse/MessageHeaderDetailsDialogFragment.java b/src/com/android/mail/browse/MessageHeaderDetailsDialogFragment.java
index b6e6603..92a4f3c 100644
--- a/src/com/android/mail/browse/MessageHeaderDetailsDialogFragment.java
+++ b/src/com/android/mail/browse/MessageHeaderDetailsDialogFragment.java
@@ -45,6 +45,7 @@
     private static final String ARG_TO = "to";
     private static final String ARG_CC = "cc";
     private static final String ARG_BCC = "bcc";
+    private static final String ARG_RECEIVED_TIME = "received-timestamp";
 
     // Public no-args constructor needed for fragment re-instantiation
     public MessageHeaderDetailsDialogFragment() {}
@@ -63,7 +64,7 @@
      */
     public static MessageHeaderDetailsDialogFragment newInstance(
             Map<String, Address> addressCache, Account account, String[] from, String[] replyTo,
-            String[] to, String[] cc, String[] bcc) {
+            String[] to, String[] cc, String[] bcc, CharSequence receivedTimestamp) {
         final MessageHeaderDetailsDialogFragment f = new MessageHeaderDetailsDialogFragment();
 
         // Supply needed items as arguments
@@ -83,6 +84,7 @@
         args.putStringArray(ARG_TO, to);
         args.putStringArray(ARG_CC, cc);
         args.putStringArray(ARG_BCC, bcc);
+        args.putCharSequence(ARG_RECEIVED_TIME, receivedTimestamp);
         f.setArguments(args);
 
         return f;
@@ -115,7 +117,7 @@
                 addressCache, (Account) args.getParcelable(ARG_ACCOUNT), null,
                 args.getStringArray(ARG_FROM), args.getStringArray(ARG_REPLY_TO),
                 args.getStringArray(ARG_TO), args.getStringArray(ARG_CC),
-                args.getStringArray(ARG_BCC));
+                args.getStringArray(ARG_BCC), args.getCharSequence(ARG_RECEIVED_TIME));
 
         expandedDetails.findViewById(R.id.details_expander)
                 .setVisibility(View.GONE);
diff --git a/src/com/android/mail/browse/MessageHeaderView.java b/src/com/android/mail/browse/MessageHeaderView.java
index 9f736d5..ed47bf5 100644
--- a/src/com/android/mail/browse/MessageHeaderView.java
+++ b/src/com/android/mail/browse/MessageHeaderView.java
@@ -1279,7 +1279,8 @@
         }
         if (!mExpandedDetailsValid) {
             renderExpandedDetails(getResources(), mExpandedDetailsView, mMessage.viaDomain,
-                    mAddressCache, getAccount(), mVeiledMatcher, mFrom, mReplyTo, mTo, mCc, mBcc);
+                    mAddressCache, getAccount(), mVeiledMatcher, mFrom, mReplyTo, mTo, mCc, mBcc,
+                    mMessageHeaderItem.getTimestampLong());
 
             mExpandedDetailsValid = true;
         }
@@ -1294,7 +1295,7 @@
     public static void renderExpandedDetails(Resources res, View detailsView,
             String viaDomain, Map<String, Address> addressCache, Account account,
             VeiledAddressMatcher veiledMatcher, String[] from, String[] replyTo,
-            String[] to, String[] cc, String[] bcc) {
+            String[] to, String[] cc, String[] bcc, CharSequence receivedTimestamp) {
         renderEmailList(res, R.id.from_heading, R.id.from_details, from, viaDomain,
                 detailsView, addressCache, account, veiledMatcher);
         renderEmailList(res, R.id.replyto_heading, R.id.replyto_details, replyTo, viaDomain,
@@ -1305,6 +1306,12 @@
                 detailsView, addressCache, account, veiledMatcher);
         renderEmailList(res, R.id.bcc_heading, R.id.bcc_details, bcc, viaDomain,
                 detailsView, addressCache, account, veiledMatcher);
+
+        // Render date
+        detailsView.findViewById(R.id.date_heading).setVisibility(VISIBLE);
+        final TextView date = (TextView) detailsView.findViewById(R.id.date_details);
+        date.setText(receivedTimestamp);
+        date.setVisibility(VISIBLE);
     }
 
     /**
@@ -1383,7 +1390,8 @@
         mDetailsPopup = (DialogFragment) manager.findFragmentByTag(DETAILS_DIALOG_TAG);
         if (mDetailsPopup == null) {
             mDetailsPopup = MessageHeaderDetailsDialogFragment.newInstance(
-                    mAddressCache, getAccount(), mFrom, mReplyTo, mTo, mCc, mBcc);
+                    mAddressCache, getAccount(), mFrom, mReplyTo, mTo, mCc, mBcc,
+                    mMessageHeaderItem.getTimestampLong());
             mDetailsPopup.show(manager, DETAILS_DIALOG_TAG);
         }
     }
diff --git a/src/com/android/mail/browse/MessageWebView.java b/src/com/android/mail/browse/MessageWebView.java
index 253a16b..9f66a8f 100644
--- a/src/com/android/mail/browse/MessageWebView.java
+++ b/src/com/android/mail/browse/MessageWebView.java
@@ -18,19 +18,45 @@
 package com.android.mail.browse;
 
 import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.webkit.WebView;
 
+import com.android.mail.utils.Clock;
+import com.android.mail.utils.LogTag;
 import com.android.mail.utils.LogUtils;
+import com.android.mail.utils.Throttle;
 
 /**
  * A WebView designed to live within a {@link MessageScrollView}.
  */
 public class MessageWebView extends WebView implements MessageScrollView.Touchable {
 
+    private static final String LOG_TAG = LogTag.getLogTag();
+
+    private static Handler sMainThreadHandler;
+
     private boolean mTouched;
 
+    private static final int MIN_RESIZE_INTERVAL = 200;
+    private static final int MAX_RESIZE_INTERVAL = 300;
+    private final Clock mClock = Clock.INSTANCE;
+
+    private final Throttle mThrottle = new Throttle("MessageWebView",
+            new Runnable() {
+                @Override public void run() {
+                    performSizeChangeDelayed();
+                }
+            }, getMainThreadHandler(),
+            MIN_RESIZE_INTERVAL, MAX_RESIZE_INTERVAL);
+
+    private int mRealWidth;
+    private int mRealHeight;
+    private boolean mIgnoreNext;
+    private long mLastSizeChangeTime = -1;
+
     public MessageWebView(Context c) {
         this(c, null);
     }
@@ -58,4 +84,51 @@
         return handled;
     }
 
+    @Override
+    protected void onSizeChanged(int w, int h, int ow, int oh) {
+        mRealWidth = w;
+        mRealHeight = h;
+        final long now = mClock.getTime();
+        boolean recentlySized = (now - mLastSizeChangeTime < MIN_RESIZE_INTERVAL);
+
+        // It's known that the previous resize event may cause a resize event immediately. If
+        // this happens sufficiently close to the last resize event, drop it on the floor.
+        if (mIgnoreNext) {
+            mIgnoreNext = false;
+            if (recentlySized) {
+                    LogUtils.w(LOG_TAG, "Suppressing size change in MessageWebView");
+                return;
+            }
+        }
+
+        if (recentlySized) {
+            mThrottle.onEvent();
+        } else {
+            // It's been a sufficiently long time - just perform the resize as normal. This should
+            // be the normal code path.
+            performSizeChange(ow, oh);
+        }
+    }
+
+    private void performSizeChange(int ow, int oh) {
+        super.onSizeChanged(mRealWidth, mRealHeight, ow, oh);
+        mLastSizeChangeTime = mClock.getTime();
+    }
+
+    private void performSizeChangeDelayed() {
+        mIgnoreNext = true;
+        performSizeChange(getWidth(), getHeight());
+    }
+
+    /**
+     * @return a {@link Handler} tied to the main thread.
+     */
+    public static Handler getMainThreadHandler() {
+        if (sMainThreadHandler == null) {
+            // No need to synchronize -- it's okay to create an extra Handler, which will be used
+            // only once and then thrown away.
+            sMainThreadHandler = new Handler(Looper.getMainLooper());
+        }
+        return sMainThreadHandler;
+    }
 }
diff --git a/src/com/android/mail/preferences/MailPrefs.java b/src/com/android/mail/preferences/MailPrefs.java
index a0f320c..f13e298 100644
--- a/src/com/android/mail/preferences/MailPrefs.java
+++ b/src/com/android/mail/preferences/MailPrefs.java
@@ -451,22 +451,4 @@
                 PreferenceKeys.GLOBAL_SYNC_OFF_DISMISSES, 0);
         getEditor().putInt(PreferenceKeys.GLOBAL_SYNC_OFF_DISMISSES, value + 1).apply();
     }
-
-    public int getNumOfDismissesForAirplaneModeOn() {
-        return getSharedPreferences().getInt(PreferenceKeys.AIRPLANE_MODE_ON_DISMISSES, 0);
-    }
-
-    public void resetNumOfDismissesForAirplaneModeOn() {
-        final int value = getSharedPreferences().getInt(
-                PreferenceKeys.AIRPLANE_MODE_ON_DISMISSES, 0);
-        if (value != 0) {
-            getEditor().putInt(PreferenceKeys.AIRPLANE_MODE_ON_DISMISSES, 0).apply();
-        }
-    }
-
-    public void incNumOfDismissesForAirplaneModeOn() {
-        final int value = getSharedPreferences().getInt(
-                PreferenceKeys.AIRPLANE_MODE_ON_DISMISSES, 0);
-        getEditor().putInt(PreferenceKeys.AIRPLANE_MODE_ON_DISMISSES, value + 1).apply();
-    }
 }
diff --git a/src/com/android/mail/ui/AbstractActivityController.java b/src/com/android/mail/ui/AbstractActivityController.java
index 9a22a60..ad59023 100644
--- a/src/com/android/mail/ui/AbstractActivityController.java
+++ b/src/com/android/mail/ui/AbstractActivityController.java
@@ -4128,6 +4128,11 @@
             if (mHasNewAccountOrFolder) {
                 refreshDrawer();
             }
+
+            // When closed, we want to use either the burger, or up, based on where we are
+            final int mode = mViewMode.getMode();
+            final boolean isTopLevel = (mFolder == null) || (mFolder.parent == Uri.EMPTY);
+            mDrawerToggle.setDrawerIndicatorEnabled(getShouldShowDrawerIndicator(mode, isTopLevel));
         }
 
         /**
@@ -4184,6 +4189,9 @@
             }
 
             mOldSlideOffset = slideOffset;
+
+            // If we're sliding, we always want to show the burger
+            mDrawerToggle.setDrawerIndicatorEnabled(true /* enable */);
         }
 
         /**
@@ -4271,23 +4279,19 @@
                 // Now try to load our parent
                 final Folder folder;
 
-                if (mFolder != null) {
-                    final Cursor cursor = mContext.getContentResolver().query(mFolder.parent,
-                            UIProvider.FOLDERS_PROJECTION, null, null, null);
+                final Cursor cursor = mContext.getContentResolver().query(mFolder.parent,
+                        UIProvider.FOLDERS_PROJECTION, null, null, null);
 
-                    if (cursor == null) {
-                        // We couldn't load the parent, so use the inbox
-                        folder = mInbox;
-                    } else {
-                        try {
-                            cursor.moveToFirst();
-                            folder = new Folder(cursor);
-                        } finally {
-                            cursor.close();
-                        }
-                    }
-                } else {
+                if (cursor == null) {
+                    // We couldn't load the parent, so use the inbox
                     folder = mInbox;
+                } else {
+                    try {
+                        cursor.moveToFirst();
+                        folder = new Folder(cursor);
+                    } finally {
+                        cursor.close();
+                    }
                 }
 
                 return folder;
diff --git a/src/com/android/mail/ui/ConversationListView.java b/src/com/android/mail/ui/ConversationListView.java
index 26e47d3..2a8fe20 100644
--- a/src/com/android/mail/ui/ConversationListView.java
+++ b/src/com/android/mail/ui/ConversationListView.java
@@ -346,14 +346,6 @@
                     mAccountPreferences.incNumOfDismissesForAccountSyncOff();
                 }
                 break;
-            case ReasonSyncOff.AIRPLANE_MODE_ON:
-                num = mMailPrefs.getNumOfDismissesForAirplaneModeOn();
-                if (num > 0 && num <= MAX_NUM_OF_SYNC_TOASTS) {
-                    Toast.makeText(getContext(), R.string.airplane_mode_on, Toast.LENGTH_SHORT)
-                            .show();
-                    mMailPrefs.incNumOfDismissesForAirplaneModeOn();
-                }
-                break;
         }
     }
 
diff --git a/src/com/android/mail/ui/ConversationSyncDisabledTipView.java b/src/com/android/mail/ui/ConversationSyncDisabledTipView.java
index b5f3977..9d37edf 100644
--- a/src/com/android/mail/ui/ConversationSyncDisabledTipView.java
+++ b/src/com/android/mail/ui/ConversationSyncDisabledTipView.java
@@ -91,8 +91,6 @@
         public static final int AUTO_SYNC_OFF = 1;
         // Global auto-sync is on, but Gmail app level sync is disabled for this particular account
         public static final int ACCOUNT_SYNC_OFF = 2;
-        // Auto-sync is enabled at both device and account level, but device is in airplane mode
-        public static final int AIRPLANE_MODE_ON = 3;
     }
 
     public ConversationSyncDisabledTipView(final Context context) {
@@ -207,8 +205,6 @@
                 return (mMailPrefs.getNumOfDismissesForAutoSyncOff() == 0);
             case ReasonSyncOff.ACCOUNT_SYNC_OFF:
                 return (mAccountPreferences.getNumOfDismissesForAccountSyncOff() == 0);
-            case ReasonSyncOff.AIRPLANE_MODE_ON:
-                return (mMailPrefs.getNumOfDismissesForAirplaneModeOn() == 0);
             default:
                 return false;
         }
@@ -219,7 +215,6 @@
         if (!ContentResolver.getMasterSyncAutomatically()) {
             // Global sync is turned off
             accountPreferences.resetNumOfDismissesForAccountSyncOff();
-            mailPrefs.resetNumOfDismissesForAirplaneModeOn();
             // Logging to track down bug where this tip is being showing when it shouldn't be.
             LogUtils.i(LOG_TAG, "getMasterSyncAutomatically() return false");
             return ReasonSyncOff.AUTO_SYNC_OFF;
@@ -235,20 +230,12 @@
             if (!TextUtils.isEmpty(account.syncAuthority) &&
                     !ContentResolver.getSyncAutomatically(acct, account.syncAuthority)) {
                 // Account level sync is off
-                mailPrefs.resetNumOfDismissesForAirplaneModeOn();
                 return ReasonSyncOff.ACCOUNT_SYNC_OFF;
             } else {
                 // Account sync is on, clear the number of times users has dismissed this
                 // warning so that next time sync is off, warning gets displayed again.
                 accountPreferences.resetNumOfDismissesForAccountSyncOff();
-
-                // Now check for whether airplane mode is on
-                if (Utils.isAirplaneModeOnAndDeviceOffline(context)) {
-                    return ReasonSyncOff.AIRPLANE_MODE_ON;
-                } else {
-                    mailPrefs.resetNumOfDismissesForAirplaneModeOn();
-                    return ReasonSyncOff.NONE;
-                }
+                return ReasonSyncOff.NONE;
             }
         }
     }
@@ -271,11 +258,6 @@
                     mTextArea.setClickable(true);
                     mTextArea.setOnClickListener(mAccountSyncOffTextClickedListener);
                     break;
-                case ReasonSyncOff.AIRPLANE_MODE_ON:
-                    mText1.setText(R.string.airplane_mode_on);
-                    mText2.setVisibility(View.GONE);
-                    mTextArea.setClickable(false);
-                    break;
                 default:
                     // Doesn't matter what mText is since this view is not displayed
             }
@@ -342,10 +324,6 @@
                 mAccountPreferences.incNumOfDismissesForAccountSyncOff();
                 reason = "account_sync_off";
                 break;
-            case ReasonSyncOff.AIRPLANE_MODE_ON:
-                mMailPrefs.incNumOfDismissesForAirplaneModeOn();
-                reason = "airplane_mode_on";
-                break;
             default:
                 reason = null;
                 break;
diff --git a/src/com/android/mail/ui/SecureConversationViewController.java b/src/com/android/mail/ui/SecureConversationViewController.java
index 7bd9254..1f9b13e 100644
--- a/src/com/android/mail/ui/SecureConversationViewController.java
+++ b/src/com/android/mail/ui/SecureConversationViewController.java
@@ -93,6 +93,7 @@
                 mCallbacks.getFragment(), mCallbacks.getHandler());
         mProgressController.instantiateProgressIndicators(rootView);
         mWebView = (MessageWebView) rootView.findViewById(R.id.webview);
+        mWebView.setOverScrollMode(View.OVER_SCROLL_NEVER);
         mWebView.setWebViewClient(mCallbacks.getWebViewClient());
         mWebView.setFocusable(false);
         final WebSettings settings = mWebView.getSettings();
diff --git a/src/com/android/mail/ui/TwoPaneController.java b/src/com/android/mail/ui/TwoPaneController.java
index 976dfa2..8588163 100644
--- a/src/com/android/mail/ui/TwoPaneController.java
+++ b/src/com/android/mail/ui/TwoPaneController.java
@@ -425,7 +425,7 @@
             // folder list has had a chance to initialize.
             final FolderListFragment folderList = getFolderListFragment();
             if (mode == ViewMode.CONVERSATION_LIST && folderList != null
-                    && mFolder.parent != Uri.EMPTY) {
+                    && mFolder != null && mFolder.parent != Uri.EMPTY) {
                 // If the user navigated via the left folders list into a child folder,
                 // back should take the user up to the parent folder's conversation list.
                 navigateUpFolderHierarchy();
@@ -580,11 +580,13 @@
         final int containerViewId = TwoPaneLayout.MISCELLANEOUS_VIEW_ID;
 
         final FragmentManager fragmentManager = mActivity.getFragmentManager();
-        final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
-        fragmentTransaction.addToBackStack(null);
-        fragmentTransaction.replace(containerViewId, fragment, TAG_CUSTOM_FRAGMENT);
-        mMiscellaneousViewTransactionId = fragmentTransaction.commitAllowingStateLoss();
-        fragmentManager.executePendingTransactions();
+        if (fragmentManager.findFragmentByTag(TAG_CUSTOM_FRAGMENT) == null) {
+            final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
+            fragmentTransaction.addToBackStack(null);
+            fragmentTransaction.replace(containerViewId, fragment, TAG_CUSTOM_FRAGMENT);
+            mMiscellaneousViewTransactionId = fragmentTransaction.commitAllowingStateLoss();
+            fragmentManager.executePendingTransactions();
+        }
 
         if (selectPosition >= 0) {
             getConversationListFragment().setRawSelected(selectPosition, true);
diff --git a/src/com/android/mail/ui/TwoPaneLayout.java b/src/com/android/mail/ui/TwoPaneLayout.java
index f4276a3..98687be 100644
--- a/src/com/android/mail/ui/TwoPaneLayout.java
+++ b/src/com/android/mail/ui/TwoPaneLayout.java
@@ -450,7 +450,7 @@
                 break;
             case ViewMode.AD:
                 dispatchConversationVisibilityChanged(false);
-                dispatchConversationListVisibilityChange(false);
+                dispatchConversationListVisibilityChange(!isConversationListCollapsed());
 
                 break;
             default:
diff --git a/src/com/android/mail/utils/Clock.java b/src/com/android/mail/utils/Clock.java
new file mode 100644
index 0000000..accd4e2
--- /dev/null
+++ b/src/com/android/mail/utils/Clock.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2010 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.utils;
+
+/**
+ * A class provide the current time (like {@link System#currentTimeMillis()}).
+ * It's intended to be mocked out for unit tests.
+ */
+public class Clock {
+    public static final Clock INSTANCE = new Clock();
+
+    protected Clock() {
+    }
+
+    public long getTime() {
+        return System.currentTimeMillis();
+    }
+}
diff --git a/src/com/android/mail/utils/Throttle.java b/src/com/android/mail/utils/Throttle.java
new file mode 100644
index 0000000..0dea3bc
--- /dev/null
+++ b/src/com/android/mail/utils/Throttle.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2010 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.utils;
+
+import android.os.Handler;
+import android.util.Log;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * This class used to "throttle" a flow of events.
+ *
+ * When {@link #onEvent()} is called, it calls the callback in a certain timeout later.
+ * Initially {@link #mMinTimeout} is used as the timeout, but if it gets multiple {@link #onEvent}
+ * calls in a certain amount of time, it extends the timeout, until it reaches {@link #mMaxTimeout}.
+ *
+ * This class is primarily used to throttle content changed events.
+ */
+public class Throttle {
+    public static final boolean DEBUG = false; // Don't submit with true
+
+    public static final int DEFAULT_MIN_TIMEOUT = 150;
+    public static final int DEFAULT_MAX_TIMEOUT = 2500;
+    /* package */ static final int TIMEOUT_EXTEND_INTERVAL = 500;
+
+    private static final String LOG_TAG = LogTag.getLogTag();
+
+    private static Timer TIMER = new Timer();
+
+    private final Clock mClock;
+    private final Timer mTimer;
+
+    /** Name of the instance.  Only for logging. */
+    private final String mName;
+
+    /** Handler for UI thread. */
+    private final Handler mHandler;
+
+    /** Callback to be called */
+    private final Runnable mCallback;
+
+    /** Minimum (default) timeout, in milliseconds.  */
+    private final int mMinTimeout;
+
+    /** Max timeout, in milliseconds.  */
+    private final int mMaxTimeout;
+
+    /** Current timeout, in milliseconds. */
+    private int mTimeout;
+
+    /** When {@link #onEvent()} was last called. */
+    private long mLastEventTime;
+
+    private MyTimerTask mRunningTimerTask;
+
+    /** Constructor with default timeout */
+    public Throttle(String name, Runnable callback, Handler handler) {
+        this(name, callback, handler, DEFAULT_MIN_TIMEOUT, DEFAULT_MAX_TIMEOUT);
+    }
+
+    /** Constructor that takes custom timeout */
+    public Throttle(String name, Runnable callback, Handler handler,int minTimeout,
+            int maxTimeout) {
+        this(name, callback, handler, minTimeout, maxTimeout, Clock.INSTANCE, TIMER);
+    }
+
+    /** Constructor for tests */
+    /* package */ Throttle(String name, Runnable callback, Handler handler,int minTimeout,
+            int maxTimeout, Clock clock, Timer timer) {
+        if (maxTimeout < minTimeout) {
+            throw new IllegalArgumentException();
+        }
+        mName = name;
+        mCallback = callback;
+        mClock = clock;
+        mTimer = timer;
+        mHandler = handler;
+        mMinTimeout = minTimeout;
+        mMaxTimeout = maxTimeout;
+        mTimeout = mMinTimeout;
+    }
+
+    private void debugLog(String message) {
+        Log.d(LOG_TAG, "Throttle: [" + mName + "] " + message);
+    }
+
+    private boolean isCallbackScheduled() {
+        return mRunningTimerTask != null;
+    }
+
+    public void cancelScheduledCallback() {
+        if (mRunningTimerTask != null) {
+            if (DEBUG) debugLog("Canceling scheduled callback");
+            mRunningTimerTask.cancel();
+            mRunningTimerTask = null;
+        }
+    }
+
+    /* package */ void updateTimeout() {
+        final long now = mClock.getTime();
+        if ((now - mLastEventTime) <= TIMEOUT_EXTEND_INTERVAL) {
+            mTimeout *= 2;
+            if (mTimeout >= mMaxTimeout) {
+                mTimeout = mMaxTimeout;
+            }
+            if (DEBUG) debugLog("Timeout extended " + mTimeout);
+        } else {
+            mTimeout = mMinTimeout;
+            if (DEBUG) debugLog("Timeout reset to " + mTimeout);
+        }
+
+        mLastEventTime = now;
+    }
+
+    public void onEvent() {
+        if (DEBUG) debugLog("onEvent");
+
+        updateTimeout();
+
+        if (isCallbackScheduled()) {
+            if (DEBUG) debugLog("    callback already scheduled");
+        } else {
+            if (DEBUG) debugLog("    scheduling callback");
+            mRunningTimerTask = new MyTimerTask();
+            mTimer.schedule(mRunningTimerTask, mTimeout);
+        }
+    }
+
+    /**
+     * Timer task called on timeout,
+     */
+    private class MyTimerTask extends TimerTask {
+        private boolean mCanceled;
+
+        @Override
+        public void run() {
+            mHandler.post(new HandlerRunnable());
+        }
+
+        @Override
+        public boolean cancel() {
+            mCanceled = true;
+            return super.cancel();
+        }
+
+        private class HandlerRunnable implements Runnable {
+            @Override
+            public void run() {
+                mRunningTimerTask = null;
+                if (!mCanceled) { // This check has to be done on the UI thread.
+                    if (DEBUG) debugLog("Kicking callback");
+                    mCallback.run();
+                }
+            }
+        }
+    }
+
+    /* package */ int getTimeoutForTest() {
+        return mTimeout;
+    }
+
+    /* package */ long getLastEventTimeForTest() {
+        return mLastEventTime;
+    }
+}
diff --git a/src/com/android/mail/utils/Utils.java b/src/com/android/mail/utils/Utils.java
index 15fb444..f3fd875 100644
--- a/src/com/android/mail/utils/Utils.java
+++ b/src/com/android/mail/utils/Utils.java
@@ -33,14 +33,11 @@
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.Typeface;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
 import android.provider.Browser;
-import android.provider.Settings;
 import android.text.Spannable;
 import android.text.SpannableString;
 import android.text.SpannableStringBuilder;
@@ -1407,23 +1404,4 @@
         }
     }
 
-    public static boolean isAirplaneModeOnAndDeviceOffline(Context context) {
-        final int airplaneMode;
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            airplaneMode = Settings.System.getInt(context.getContentResolver(),
-                    Settings.System.AIRPLANE_MODE_ON, 0);
-        } else {
-            airplaneMode = Settings.Global.getInt(context.getContentResolver(),
-                    Settings.Global.AIRPLANE_MODE_ON, 0);
-        }
-        if (airplaneMode == 0) {
-            return false;
-        }
-        // Otherwise check if device is online, since it's possible to still use
-        // wifi when airplane mode is on
-        ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(
-                Context.CONNECTIVITY_SERVICE);
-        NetworkInfo info = connectivityManager.getActiveNetworkInfo();
-        return (info == null || !info.isConnected());
-    }
 }