Merge "Remember our scroll position when we return to conv list" into jb-ub-mail-ur10
diff --git a/src/com/android/mail/ui/AbstractActivityController.java b/src/com/android/mail/ui/AbstractActivityController.java
index fe38134..f2c28d8 100644
--- a/src/com/android/mail/ui/AbstractActivityController.java
+++ b/src/com/android/mail/ui/AbstractActivityController.java
@@ -45,6 +45,7 @@
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.Parcelable;
 import android.provider.SearchRecentSuggestions;
 import android.support.v4.app.ActionBarDrawerToggle;
 import android.support.v4.widget.DrawerLayout;
@@ -158,7 +159,10 @@
     /** Tag for {@link #mDetachedConvUri} */
     private static final String SAVED_DETACHED_CONV_URI = "saved-detached-conv-uri";
     /** Key to store {@link #mInbox}. */
-    private final static String SAVED_INBOX_KEY = "m-inbox";
+    private static final String SAVED_INBOX_KEY = "m-inbox";
+    /** Key to store {@link #mConversationListScrollPositions} */
+    private static final String SAVED_CONVERSATION_LIST_SCROLL_POSITIONS =
+            "saved-conversation-list-scroll-positions";
 
     /** Tag  used when loading a wait fragment */
     protected static final String TAG_WAIT = "wait-fragment";
@@ -189,6 +193,9 @@
      */
     private Uri mDetachedConvUri;
 
+    /** A map of {@link Folder} {@link Uri} to scroll position in the conversation list. */
+    private final Bundle mConversationListScrollPositions = new Bundle();
+
     /** A {@link android.content.BroadcastReceiver} that suppresses new e-mail notifications. */
     private SuppressNotificationReceiver mNewEmailReceiver = null;
 
@@ -2043,6 +2050,9 @@
         mSafeToModifyFragments = false;
 
         outState.putParcelable(SAVED_INBOX_KEY, mInbox);
+
+        outState.putBundle(SAVED_CONVERSATION_LIST_SCROLL_POSITIONS,
+                mConversationListScrollPositions);
     }
 
     /**
@@ -2260,6 +2270,10 @@
         }
 
         mInbox = savedState.getParcelable(SAVED_INBOX_KEY);
+
+        mConversationListScrollPositions.clear();
+        mConversationListScrollPositions.putAll(
+                savedState.getBundle(SAVED_CONVERSATION_LIST_SCROLL_POSITIONS));
     }
 
     /**
@@ -4311,4 +4325,15 @@
             }
         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
     }
+
+    @Override
+    public Parcelable getConversationListScrollPosition(final String folderUri) {
+        return mConversationListScrollPositions.getParcelable(folderUri);
+    }
+
+    @Override
+    public void setConversationListScrollPosition(final String folderUri,
+            final Parcelable savedPosition) {
+        mConversationListScrollPositions.putParcelable(folderUri, savedPosition);
+    }
 }
diff --git a/src/com/android/mail/ui/AnimatedAdapter.java b/src/com/android/mail/ui/AnimatedAdapter.java
index a3b65ea..fad7b3a 100644
--- a/src/com/android/mail/ui/AnimatedAdapter.java
+++ b/src/com/android/mail/ui/AnimatedAdapter.java
@@ -730,10 +730,16 @@
 
     @Override
     public long getItemId(int position) {
-        if (mShowFooter && position == getCount() - 1
-                || mSpecialViews.get(position) != null) {
+        if (mShowFooter && position == getCount() - 1) {
             return -1;
         }
+
+        final ConversationSpecialItemView specialView = mSpecialViews.get(position);
+        if (specialView != null) {
+            // TODO(skennedy) We probably want something better than this
+            return specialView.hashCode();
+        }
+
         final int cursorPos = position - getPositionOffset(position);
         // advance the cursor to the right position and read the cached conversation, if present
         //
diff --git a/src/com/android/mail/ui/ConversationListCallbacks.java b/src/com/android/mail/ui/ConversationListCallbacks.java
index ff3f83c..23f2873 100644
--- a/src/com/android/mail/ui/ConversationListCallbacks.java
+++ b/src/com/android/mail/ui/ConversationListCallbacks.java
@@ -19,6 +19,8 @@
 
 import android.app.LoaderManager.LoaderCallbacks;
 import android.database.DataSetObserver;
+import android.os.Bundle;
+import android.os.Parcelable;
 
 import com.android.mail.browse.ConversationCursor;
 import com.android.mail.providers.Conversation;
@@ -80,7 +82,7 @@
     void commitDestructiveActions(boolean animate);
 
     /**
-     * Detect if there are any animations occuring in the conversation list.
+     * Detect if there are any animations occurring in the conversation list.
      */
     boolean isAnimating();
 
@@ -88,4 +90,26 @@
      * Tell the controller that the conversation view has entered detached mode.
      */
     void setDetachedMode();
+
+    String CONVERSATION_LIST_SCROLL_POSITION_INDEX = "index";
+    String CONVERSATION_LIST_SCROLL_POSITION_OFFSET = "offset";
+
+    /**
+     * Gets the last save scroll position of the conversation list for the specified Folder.
+     *
+     * @return A {@link Bundle} containing two ints,
+     *         {@link #CONVERSATION_LIST_SCROLL_POSITION_INDEX} and
+     *         {@link #CONVERSATION_LIST_SCROLL_POSITION_OFFSET}, or <code>null</code>
+     */
+    Parcelable getConversationListScrollPosition(String folderUri);
+
+    /**
+     * Sets the last save scroll position of the conversation list for the specified Folder for
+     * restoration on returning to this list.
+     *
+     * @param savedPosition A {@link Bundle} containing two ints,
+     *            {@link #CONVERSATION_LIST_SCROLL_POSITION_INDEX} and
+     *            {@link #CONVERSATION_LIST_SCROLL_POSITION_OFFSET}
+     */
+    void setConversationListScrollPosition(String folderUri, Parcelable savedPosition);
 }
diff --git a/src/com/android/mail/ui/ConversationListFragment.java b/src/com/android/mail/ui/ConversationListFragment.java
index 4d8ecad..32b8b3d 100644
--- a/src/com/android/mail/ui/ConversationListFragment.java
+++ b/src/com/android/mail/ui/ConversationListFragment.java
@@ -27,6 +27,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.Parcelable;
 import android.text.format.DateUtils;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -155,6 +156,12 @@
     private long mSelectionModeExitedTimestamp = -1;
 
     /**
+     * If <code>true</code>, we have restored (or attempted to restore) the list's scroll position
+     * from when we were last on this conversation list.
+     */
+    private boolean mScrollPositionRestored = false;
+
+    /**
      * If the current list is for a folder with children, this set of loader callbacks will
      * create a loader for all the child folders, and will return an {@link ObjectCursor} over the
      * list.
@@ -628,6 +635,8 @@
         final ConversationCursor conversationCursor = getConversationListCursor();
         if (conversationCursor != null) {
             conversationCursor.handleNotificationActions();
+
+            restoreLastScrolledPosition();
         }
 
         mSelectedSet.addObserver(mConversationSetObserver);
@@ -638,6 +647,8 @@
         super.onPause();
 
         mSelectedSet.removeObserver(mConversationSetObserver);
+
+        saveLastScrolledPosition();
     }
 
     @Override
@@ -939,6 +950,12 @@
         // Check against the previous cursor here and see if they are the same. If they are, then
         // do a notifyDataSetChanged.
         final ConversationCursor newCursor = mCallbacks.getConversationListCursor();
+
+        if (newCursor == null && mListAdapter.getCursor() != null) {
+            // We're losing our cursor, so save our scroll position
+            saveLastScrolledPosition();
+        }
+
         mListAdapter.swapCursor(newCursor);
         // When the conversation cursor is *updated*, we get back the same instance. In that
         // situation, CursorAdapter.swapCursor() silently returns, without forcing a
@@ -952,17 +969,17 @@
 
         if (newCursor != null && newCursor.getCount() > 0) {
             newCursor.markContentsSeen();
+            restoreLastScrolledPosition();
         }
 
         // If a current conversation is available, and none is selected in the list, then ask
         // the list to select the current conversation.
         final Conversation conv = mCallbacks.getCurrentConversation();
-        if (conv == null) {
-            return;
-        }
-        if (mListView.getChoiceMode() != ListView.CHOICE_MODE_NONE
-                && mListView.getCheckedItemPosition() == -1) {
-            setSelected(conv.position, true);
+        if (conv != null) {
+            if (mListView.getChoiceMode() != ListView.CHOICE_MODE_NONE
+                    && mListView.getCheckedItemPosition() == -1) {
+                setSelected(conv.position, true);
+            }
         }
     }
 
@@ -1020,4 +1037,28 @@
             // Do nothing
         }
     };
+
+    private void saveLastScrolledPosition() {
+        if (mListAdapter.getCursor() == null) {
+            // If you save your scroll position in an empty list, you're gonna have a bad time
+            return;
+        }
+
+        final Parcelable savedState = mListView.onSaveInstanceState();
+
+        mActivity.getListHandler().setConversationListScrollPosition(
+                mFolder.conversationListUri.toString(), savedState);
+    }
+
+    private void restoreLastScrolledPosition() {
+        // Scroll to our previous position, if necessary
+        if (!mScrollPositionRestored) {
+            final Parcelable savedState = mActivity.getListHandler()
+                    .getConversationListScrollPosition(mFolder.conversationListUri.toString());
+            if (savedState != null) {
+                mListView.onRestoreInstanceState(savedState);
+            }
+            mScrollPositionRestored = true;
+        }
+    }
 }