Preserving list state on orientation change

Change-Id: Ibb75905573428e244b0cc4f0e61418e9f38101ed
diff --git a/src/com/android/contacts/activities/ContactListActivity.java b/src/com/android/contacts/activities/ContactListActivity.java
index 5e7abb4..c483d0b 100644
--- a/src/com/android/contacts/activities/ContactListActivity.java
+++ b/src/com/android/contacts/activities/ContactListActivity.java
@@ -39,6 +39,7 @@
 import android.app.ActionBar;
 import android.app.Activity;
 import android.app.Dialog;
+import android.app.Fragment;
 import android.app.FragmentTransaction;
 import android.content.ContentValues;
 import android.content.Intent;
@@ -65,6 +66,8 @@
 
     private static final String TAG = "ContactListActivity";
 
+    private static final String KEY_MODE = "mode";
+
     private static final int SUBACTIVITY_NEW_CONTACT = 1;
     private static final int SUBACTIVITY_VIEW_CONTACT = 2;
     private static final int SUBACTIVITY_DISPLAY_GROUP = 3;
@@ -102,9 +105,21 @@
     }
 
     @Override
+    public void onAttachFragment(Fragment fragment) {
+        if (fragment instanceof ContactBrowseListFragment) {
+            mListFragment = (ContactBrowseListFragment)fragment;
+            mListFragment.setOnContactListActionListener(new ContactBrowserActionListener());
+        }
+    }
+
+    @Override
     protected void onCreate(Bundle savedState) {
         super.onCreate(savedState);
 
+        if (savedState != null) {
+            mMode = savedState.getInt(KEY_MODE);
+        }
+
         // Extract relevant information from the intent
         mRequest = mIntentResolver.resolveIntent(getIntent());
         if (!mRequest.isValid()) {
@@ -176,7 +191,9 @@
         View navBarView = mNavigationBar.onCreateView(getLayoutInflater());
         actionBar.setCustomNavigationMode(navBarView);
 
-        configureListFragment();
+        if (mListFragment == null) {
+            configureListFragment();
+        }
 
         setupContactDetailFragment();
 
@@ -202,9 +219,7 @@
             return;
         }
 
-        if (mListFragment != null) {
-            mListFragment.setOnContactListActionListener(null);
-        }
+        closeListFragment();
 
         mMode = mode;
         switch (mMode) {
@@ -227,11 +242,28 @@
             }
         }
 
+        Bundle savedState = mNavigationBar.getSavedStateForMode(mMode);
+        if (savedState != null) {
+            mListFragment.restoreSavedState(savedState);
+        }
+
         openFragmentTransaction()
                 .replace(R.id.two_pane_list, mListFragment)
                 .commit();
     }
 
+    private void closeListFragment() {
+        if (mListFragment != null) {
+            mListFragment.setOnContactListActionListener(null);
+
+            if (mNavigationBar != null) {
+                Bundle state = new Bundle();
+                mListFragment.onSaveInstanceState(state);
+                mNavigationBar.saveStateForMode(mMode, state);
+            }
+        }
+    }
+
     private void setupContactDetailFragment() {
         // No editor here
         closeEditorFragment();
@@ -736,6 +768,7 @@
     @Override
     protected void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
+        outState.putInt(KEY_MODE, mMode);
         if (mNavigationBar != null) {
             mNavigationBar.onSaveInstanceState(outState);
         }
diff --git a/src/com/android/contacts/activities/NavigationBar.java b/src/com/android/contacts/activities/NavigationBar.java
index 58b2d35..5b6caa6 100644
--- a/src/com/android/contacts/activities/NavigationBar.java
+++ b/src/com/android/contacts/activities/NavigationBar.java
@@ -30,6 +30,8 @@
 import android.widget.ImageView;
 import android.widget.ToggleButton;
 
+import java.util.HashMap;
+
 /**
  * Navigation bar at the top of the Contacts activity.
  */
@@ -43,6 +45,10 @@
     private static final String EXTRA_KEY_MODE = "navBar.mode";
     private static final String EXTRA_KEY_QUERY = "navBar.query";
 
+    private static final String KEY_MODE_CONTACTS = "mode_contacts";
+    private static final String KEY_MODE_FAVORITES = "mode_favorites";
+    private static final String KEY_MODE_SEARCH = "mode_search";
+
     public static final int MODE_CONTACTS = 0;
     public static final int MODE_FAVORITES = 1;
     public static final int MODE_SEARCH = 2;
@@ -50,6 +56,9 @@
     private int mMode = MODE_CONTACTS;
     private int mDefaultMode = MODE_CONTACTS;
     private String mQueryString;
+    private HashMap<Integer, Bundle> mSavedStateByMode = new HashMap<Integer, Bundle>();
+
+
     private SearchEditText mSearchEditText;
     private View mNavigationBar;
 
@@ -74,6 +83,9 @@
             mDefaultMode = savedState.getInt(EXTRA_KEY_DEFAULT_MODE, -1);
             mMode = savedState.getInt(EXTRA_KEY_MODE, -1);
             mQueryString = savedState.getString(EXTRA_KEY_QUERY);
+            restoreSavedState(savedState, MODE_CONTACTS, KEY_MODE_CONTACTS);
+            restoreSavedState(savedState, MODE_FAVORITES, KEY_MODE_FAVORITES);
+            restoreSavedState(savedState, MODE_SEARCH, KEY_MODE_SEARCH);
         }
 
         int actionCode = request.getActionCode();
@@ -206,9 +218,36 @@
         setMode(mDefaultMode);
     }
 
+    public void saveStateForMode(int mode, Bundle state) {
+        mSavedStateByMode.put(mode, state);
+    }
+
+    public Bundle getSavedStateForMode(int mode) {
+        return mSavedStateByMode.get(mode);
+    }
+
     public void onSaveInstanceState(Bundle outState) {
         outState.putInt(EXTRA_KEY_DEFAULT_MODE, mDefaultMode);
         outState.putInt(EXTRA_KEY_MODE, mMode);
         outState.putString(EXTRA_KEY_QUERY, mQueryString);
+        saveInstanceState(outState, MODE_CONTACTS, KEY_MODE_CONTACTS);
+        saveInstanceState(outState, MODE_FAVORITES, KEY_MODE_FAVORITES);
+        saveInstanceState(outState, MODE_SEARCH, KEY_MODE_SEARCH);
+    }
+
+    private void saveInstanceState(Bundle outState, int mode, String key) {
+        Bundle state = mSavedStateByMode.get(mode);
+        if (state != null) {
+            outState.putParcelable(key, state);
+        }
+    }
+
+    private void restoreSavedState(Bundle savedState, int mode, String key) {
+        Bundle bundle = savedState.getParcelable(key);
+        if (bundle == null) {
+            mSavedStateByMode.remove(mode);
+        } else {
+            mSavedStateByMode.put(mode, bundle);
+        }
     }
 }
diff --git a/src/com/android/contacts/list/ContactEntryListFragment.java b/src/com/android/contacts/list/ContactEntryListFragment.java
index 0602097..2eedf0c 100644
--- a/src/com/android/contacts/list/ContactEntryListFragment.java
+++ b/src/com/android/contacts/list/ContactEntryListFragment.java
@@ -22,9 +22,9 @@
 import com.android.contacts.ContactsSearchManager;
 import com.android.contacts.R;
 import com.android.contacts.ui.ContactsPreferences;
+import com.android.contacts.widget.CompositeCursorAdapter.Partition;
 import com.android.contacts.widget.ContextMenuAdapter;
 import com.android.contacts.widget.InstrumentedLoaderManagingFragment;
-import com.android.contacts.widget.CompositeCursorAdapter.Partition;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
@@ -43,28 +43,27 @@
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.provider.ContactsContract;
-import android.provider.Settings;
 import android.provider.ContactsContract.Directory;
 import android.provider.ContactsContract.ProviderStatus;
+import android.provider.Settings;
 import android.telephony.TelephonyManager;
-import android.text.Html;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.View.OnClickListener;
 import android.view.View.OnFocusChangeListener;
 import android.view.View.OnTouchListener;
+import android.view.ViewGroup;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.AbsListView;
+import android.widget.AbsListView.OnScrollListener;
 import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
 import android.widget.Button;
 import android.widget.ListView;
 import android.widget.TextView;
-import android.widget.AbsListView.OnScrollListener;
-import android.widget.AdapterView.OnItemClickListener;
 
 /**
  * Common base class for various contact-related list fragments.
@@ -77,7 +76,16 @@
 
     private static final String TAG = "ContactEntryListFragment";
 
-    private static final String LIST_STATE_KEY = "liststate";
+    private static final String KEY_LIST_STATE = "liststate";
+    private static final String KEY_SECTION_HEADER_DISPLAY_ENABLED = "sectionHeaderDisplayEnabled";
+    private static final String KEY_PHOTO_LOADER_ENABLED = "photoLoaderEnabled";
+    private static final String KEY_SEARCH_MODE = "searchMode";
+    private static final String KEY_AIZY_ENABLED = "aizyEnabled";
+    private static final String KEY_QUERY_STRING = "queryString";
+    private static final String KEY_DIRECTORY_SEARCH_ENABLED = "directorySearchEnabled";
+    private static final String KEY_SELECTION_VISIBLE = "selectionVisible";
+    private static final String KEY_REQUEST = "request";
+    private static final String KEY_LEGACY_COMPATIBILITY = "legacyCompatibility";
 
     private static final String DIRECTORY_ID_ARG_KEY = "directoryId";
 
@@ -90,6 +98,8 @@
     private String mQueryString;
     private boolean mDirectorySearchEnabled;
     private boolean mSelectionVisible;
+    private ContactsRequest mRequest;
+    private boolean mLegacyCompatibility;
 
     private T mAdapter;
     private View mView;
@@ -101,7 +111,6 @@
      */
     private Parcelable mListState;
 
-    private boolean mLegacyCompatibility;
     private int mDisplayOrder;
     private int mSortOrder;
 
@@ -122,8 +131,6 @@
      */
     private boolean mLoadPriorityDirectoriesOnly;
 
-    private ContactsRequest mRequest;
-
     private Context mContext;
 
     protected abstract View inflateView(LayoutInflater inflater, ViewGroup container);
@@ -175,6 +182,49 @@
     protected void onInitializeLoaders() {
     }
 
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putBoolean(KEY_SECTION_HEADER_DISPLAY_ENABLED, mSectionHeaderDisplayEnabled);
+        outState.putBoolean(KEY_PHOTO_LOADER_ENABLED, mPhotoLoaderEnabled);
+        outState.putBoolean(KEY_SEARCH_MODE, mSearchMode);
+        outState.putBoolean(KEY_AIZY_ENABLED, mAizyEnabled);
+        outState.putBoolean(KEY_DIRECTORY_SEARCH_ENABLED, mDirectorySearchEnabled);
+        outState.putBoolean(KEY_SELECTION_VISIBLE, mSelectionVisible);
+        outState.putBoolean(KEY_LEGACY_COMPATIBILITY, mLegacyCompatibility);
+        outState.putString(KEY_QUERY_STRING, mQueryString);
+        outState.putParcelable(KEY_REQUEST, mRequest);
+
+        if (mListView != null) {
+            outState.putParcelable(KEY_LIST_STATE, mListView.onSaveInstanceState());
+        }
+    }
+
+    @Override
+    public void onCreate(Bundle savedState) {
+        super.onCreate(savedState);
+        restoreSavedState(savedState);
+    }
+
+    public void restoreSavedState(Bundle savedState) {
+        if (savedState == null) {
+            return;
+        }
+
+        mSectionHeaderDisplayEnabled = savedState.getBoolean(KEY_SECTION_HEADER_DISPLAY_ENABLED);
+        mPhotoLoaderEnabled = savedState.getBoolean(KEY_PHOTO_LOADER_ENABLED);
+        mSearchMode = savedState.getBoolean(KEY_SEARCH_MODE);
+        mAizyEnabled = savedState.getBoolean(KEY_AIZY_ENABLED);
+        mDirectorySearchEnabled = savedState.getBoolean(KEY_DIRECTORY_SEARCH_ENABLED);
+        mSelectionVisible = savedState.getBoolean(KEY_SELECTION_VISIBLE);
+        mLegacyCompatibility = savedState.getBoolean(KEY_LEGACY_COMPATIBILITY);
+        mQueryString = savedState.getString(KEY_QUERY_STRING);
+        mRequest = savedState.getParcelable(KEY_REQUEST);
+
+        // Retrieve list state. This will be applied in onLoadFinished
+        mListState = savedState.getParcelable(KEY_LIST_STATE);
+    }
+
     /**
      * Returns the parsed intent that started the activity hosting this fragment.
      */
@@ -457,11 +507,11 @@
         mLegacyCompatibility = flag;
     }
 
-    public int getContactNameDisplayOrder() {
+    protected int getContactNameDisplayOrder() {
         return mDisplayOrder;
     }
 
-    public void setContactNameDisplayOrder(int displayOrder) {
+    protected void setContactNameDisplayOrder(int displayOrder) {
         mDisplayOrder = displayOrder;
         if (mAdapter != null) {
             mAdapter.setContactNameDisplayOrder(displayOrder);
@@ -496,15 +546,6 @@
     }
 
     @Override
-    public void onCreate(Bundle savedState) {
-        super.onCreate(savedState);
-        // Retrieve list state. This will be applied in onLoadFinished
-        if (savedState != null) {
-            mListState = savedState.getParcelable(LIST_STATE_KEY);
-        }
-    }
-
-    @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
         onCreateView(inflater, container);
@@ -689,15 +730,6 @@
         finish();
     }
 
-    @Override
-    public void onSaveInstanceState(Bundle icicle) {
-        super.onSaveInstanceState(icicle);
-        if (mListView != null) {
-            mListState = mListView.onSaveInstanceState();
-            icicle.putParcelable(LIST_STATE_KEY, mListState);
-        }
-    }
-
     /**
      * Restore the list state after the adapter is populated.
      */
diff --git a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
index a89de48..2133d49 100644
--- a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
@@ -20,6 +20,7 @@
 
 import android.content.SharedPreferences;
 import android.database.Cursor;
+import android.os.Bundle;
 import android.preference.PreferenceManager;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -33,6 +34,11 @@
  */
 public class DefaultContactBrowseListFragment extends ContactBrowseListFragment {
 
+    private static final String KEY_EDIT_MODE = "editMode";
+    private static final String KEY_CREATE_CONTACT_ENABLED = "createContactEnabled";
+    private static final String KEY_DISPLAY_WITH_PHONES_ONLY = "displayWithPhonesOnly";
+    private static final String KEY_VISIBLE_CONTACTS_RESTRICTION = "visibleContactsRestriction";
+
     private boolean mEditMode;
     private boolean mCreateContactEnabled;
     private int mDisplayWithPhonesOnlyOption = ContactsRequest.DISPLAY_ONLY_WITH_PHONES_DISABLED;
@@ -46,6 +52,30 @@
     }
 
     @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putBoolean(KEY_EDIT_MODE, mEditMode);
+        outState.putBoolean(KEY_CREATE_CONTACT_ENABLED, mCreateContactEnabled);
+        outState.putInt(KEY_DISPLAY_WITH_PHONES_ONLY, mDisplayWithPhonesOnlyOption);
+        outState.putBoolean(KEY_VISIBLE_CONTACTS_RESTRICTION, mVisibleContactsRestrictionEnabled);
+    }
+
+    @Override
+    public void restoreSavedState(Bundle savedState) {
+        super.restoreSavedState(savedState);
+
+        if (savedState == null) {
+            return;
+        }
+
+        mEditMode = savedState.getBoolean(KEY_EDIT_MODE);
+        mCreateContactEnabled = savedState.getBoolean(KEY_CREATE_CONTACT_ENABLED);
+        mDisplayWithPhonesOnlyOption = savedState.getInt(KEY_DISPLAY_WITH_PHONES_ONLY);
+        mVisibleContactsRestrictionEnabled =
+                savedState.getBoolean(KEY_VISIBLE_CONTACTS_RESTRICTION);
+    }
+
+    @Override
     protected void prepareEmptyView() {
         if (isShowingContactsWithPhonesOnly()) {
             setEmptyText(R.string.noContactsWithPhoneNumbers);