Now using loaders and LoaderManagingFragment

The change is partial and for now Favorites and
lots of other things are broken.

Change-Id: I8dc6104e499c094ea344f4ecd0819386150a8b0c
diff --git a/src/android/app/patterns/CursorLoader.java b/src/android/app/patterns/CursorLoader.java
index e7b2e41..04b58f5 100644
--- a/src/android/app/patterns/CursorLoader.java
+++ b/src/android/app/patterns/CursorLoader.java
@@ -138,4 +138,12 @@
     public void setSelectionArgs(String[] selectionArgs) {
         mSelectionArgs = selectionArgs;
     }
+
+    public String getSortOrder() {
+        return mSortOrder;
+    }
+
+    public void setSortOrder(String sortOrder) {
+        mSortOrder = sortOrder;
+    }
 }
diff --git a/src/android/app/patterns/LoaderManagingFragment.java b/src/android/app/patterns/LoaderManagingFragment.java
index 32e3bf3..e0a464b 100644
--- a/src/android/app/patterns/LoaderManagingFragment.java
+++ b/src/android/app/patterns/LoaderManagingFragment.java
@@ -135,7 +135,7 @@
             // The loader isn't getting passed along to the next instance so ask it to stop loading
 // TODO: uncomment once isChangingConfig method is available
 //            if (!getActivity().isChangingConfigurations()) {
-//                loader.stopLoading();
+                loader.stopLoading();
 //            }
         }
 
diff --git a/src/com/android/contacts/ContactsListActivity.java b/src/com/android/contacts/ContactsListActivity.java
index e1cab30..5d061f7 100644
--- a/src/com/android/contacts/ContactsListActivity.java
+++ b/src/com/android/contacts/ContactsListActivity.java
@@ -515,7 +515,8 @@
             case MODE_INSERT_OR_EDIT_CONTACT:
             case MODE_QUERY_PICK_TO_EDIT:
             case MODE_STREQUENT:
-            case MODE_FREQUENT: {
+            case MODE_FREQUENT:
+            case MODE_QUERY: {
                 ContactBrowseListFragment fragment = new ContactBrowseListFragment();
                 if (!mSearchMode) {
                     fragment.setSectionHeaderDisplayEnabled(true);
@@ -530,6 +531,10 @@
                     fragment.setCreateContactEnabled(true);
                 }
 
+                if (mMode == MODE_QUERY) {
+                    fragment.setSearchResultsMode(true);
+                }
+
                 fragment.setOnContactListActionListener(new OnContactBrowserActionListener() {
                     public void onSearchAllContactsAction(String string) {
                         doSearch();
@@ -652,6 +657,8 @@
         mListFragment.setSearchMode(mSearchMode);
         mListFragment.setSearchResultsMode(mSearchResultsMode);
         mListFragment.setQueryString(mInitialFilter);
+        mListFragment.setContactNameDisplayOrder(mContactsPrefs.getDisplayOrder());
+        mListFragment.setSortOrder(mContactsPrefs.getSortOrder());
 
         if ((mMode & MODE_MASK_SHOW_PHOTOS) == MODE_MASK_SHOW_PHOTOS) {
             mListFragment.setPhotoLoaderEnabled(true);
@@ -743,6 +750,12 @@
     protected void onResume() {
         super.onResume();
 
+        // Move to the fragment
+        if (mListFragment != null) {
+            mListFragment.setContactNameDisplayOrder(mContactsPrefs.getDisplayOrder());
+            mListFragment.setSortOrder(mContactsPrefs.getSortOrder());
+        }
+
         // TODO move this to onAttach of the corresponding fragment
         mListView = (ListView) findViewById(android.R.id.list);
 
@@ -1922,6 +1935,7 @@
 
         if (mListFragment != null) {
             mListFragment.setContactNameDisplayOrder(mDisplayOrder);
+            mListFragment.setSortOrder(mSortOrder);
         }
 
         if (mListView instanceof ContactEntryListView) {
diff --git a/src/com/android/contacts/list/ContactBrowseListFragment.java b/src/com/android/contacts/list/ContactBrowseListFragment.java
index 0fc025e..97c7a1c 100644
--- a/src/com/android/contacts/list/ContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/ContactBrowseListFragment.java
@@ -18,7 +18,11 @@
 import com.android.contacts.ContactsListActivity;
 import com.android.contacts.R;
 
+import android.app.patterns.CursorLoader;
+import android.app.patterns.Loader;
+import android.database.Cursor;
 import android.net.Uri;
+import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -33,6 +37,20 @@
     private boolean mEditMode;
     private boolean mCreateContactEnabled;
 
+    private CursorLoader mLoader;
+
+    @Override
+    protected CursorLoader onCreateLoader(int id, Bundle args) {
+        mLoader = new CursorLoader(getActivity(), null, null, null, null, null);
+        return mLoader;
+    }
+
+    @Override
+    protected void reloadData() {
+        getAdapter().configureLoader(mLoader);
+        mLoader.forceLoad();
+    }
+
     public void setOnContactListActionListener(OnContactBrowserActionListener listener) {
         mListener = listener;
     }
@@ -42,6 +60,16 @@
     }
 
     @Override
+    protected void onInitializeLoaders() {
+        startLoading(0, null);
+    }
+
+    @Override
+    protected void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+        getAdapter().changeCursor(data);
+    }
+
+    @Override
     protected void onItemClick(int position, long id) {
         if (isSearchAllContactsItemPosition(position)) {
             mListener.onSearchAllContactsAction((String)null);
@@ -66,6 +94,13 @@
                 new ContactItemListAdapter((ContactsListActivity)getActivity());
         adapter.setSectionHeaderDisplayEnabled(isSectionHeaderDisplayEnabled());
         adapter.setDisplayPhotos(isPhotoLoaderEnabled());
+        adapter.setSearchMode(isSearchMode());
+        adapter.setSearchResultsMode(isSearchResultsMode());
+        adapter.setQueryString(getQueryString());
+        adapter.setContactNameDisplayOrder(getContactNameDisplayOrder());
+        adapter.setSortOrder(getSortOrder());
+
+        adapter.configureLoader(mLoader);
         return adapter;
     }
 
@@ -129,4 +164,5 @@
         super.finish();
         mListener.onFinishAction();
     }
+
 }
diff --git a/src/com/android/contacts/list/ContactEntryListAdapter.java b/src/com/android/contacts/list/ContactEntryListAdapter.java
index e5b9087..dbd6561 100644
--- a/src/com/android/contacts/list/ContactEntryListAdapter.java
+++ b/src/com/android/contacts/list/ContactEntryListAdapter.java
@@ -19,12 +19,16 @@
 import com.android.contacts.widget.TextWithHighlighting;
 import com.android.contacts.widget.TextWithHighlightingFactory;
 
+import android.app.patterns.CursorLoader;
 import android.content.Context;
 import android.database.Cursor;
 import android.database.DatabaseUtils;
 import android.net.Uri;
 import android.provider.ContactsContract;
+import android.provider.ContactsContract.ContactCounts;
 import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.SearchSnippetColumns;
+import android.text.TextUtils;
 import android.widget.ListView;
 
 /**
@@ -33,6 +37,39 @@
  */
 public abstract class ContactEntryListAdapter extends PinnedHeaderListAdapter {
 
+    // TODO move to type-specific adapter
+    static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
+        Contacts._ID,                       // 0
+        Contacts.DISPLAY_NAME_PRIMARY,      // 1
+        Contacts.DISPLAY_NAME_ALTERNATIVE,  // 2
+        Contacts.SORT_KEY_PRIMARY,          // 3
+        Contacts.STARRED,                   // 4
+        Contacts.TIMES_CONTACTED,           // 5
+        Contacts.CONTACT_PRESENCE,          // 6
+        Contacts.PHOTO_ID,                  // 7
+        Contacts.LOOKUP_KEY,                // 8
+        Contacts.PHONETIC_NAME,             // 9
+        Contacts.HAS_PHONE_NUMBER,          // 10
+    };
+
+    // TODO move to type-specific adapter
+    static final String[] CONTACTS_SUMMARY_FILTER_PROJECTION = new String[] {
+        Contacts._ID,                       // 0
+        Contacts.DISPLAY_NAME_PRIMARY,      // 1
+        Contacts.DISPLAY_NAME_ALTERNATIVE,  // 2
+        Contacts.SORT_KEY_PRIMARY,          // 3
+        Contacts.STARRED,                   // 4
+        Contacts.TIMES_CONTACTED,           // 5
+        Contacts.CONTACT_PRESENCE,          // 6
+        Contacts.PHOTO_ID,                  // 7
+        Contacts.LOOKUP_KEY,                // 8
+        Contacts.PHONETIC_NAME,             // 9
+        Contacts.HAS_PHONE_NUMBER,          // 10
+        SearchSnippetColumns.SNIPPET_MIMETYPE, // 11
+        SearchSnippetColumns.SNIPPET_DATA1,     // 12
+        SearchSnippetColumns.SNIPPET_DATA4,     // 13
+    };
+
     // TODO move to a type-specific adapter
     public static final int SUMMARY_ID_COLUMN_INDEX = 0;
     public static final int SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX = 1;
@@ -47,16 +84,39 @@
     private TextWithHighlightingFactory mTextWithHighlightingFactory;
 
     private int mDisplayOrder;
+    private int mSortOrder;
     private boolean mNameHighlightingEnabled;
     private ContactPhotoLoader mPhotoLoader;
+    private boolean mSectionHeaderDisplayEnabled;
 
-    // TODO move to Loader
-    protected String mQueryString;
+    private String mQueryString;
+    private boolean mSearchMode;
+    private boolean mSearchResultsMode;
 
     public ContactEntryListAdapter(Context context) {
         super(context);
     }
 
+    public boolean isSearchMode() {
+        return mSearchMode;
+    }
+
+    public void setSearchMode(boolean flag) {
+        mSearchMode = flag;
+    }
+
+    public boolean isSearchResultsMode() {
+        return mSearchResultsMode;
+    }
+
+    public void setSearchResultsMode(boolean searchResultsMode) {
+        mSearchResultsMode = searchResultsMode;
+    }
+
+    public String getQueryString() {
+        return mQueryString;
+    }
+
     public void setQueryString(String queryString) {
         mQueryString = queryString;
     }
@@ -65,14 +125,30 @@
         return mContext;
     }
 
-    public void setContactNameDisplayOrder(int displayOrder) {
-        mDisplayOrder = displayOrder;
+    public boolean isSectionHeaderDisplayEnabled() {
+        return mSectionHeaderDisplayEnabled;
+    }
+
+    public void setSectionHeaderDisplayEnabled(boolean flag) {
+        mSectionHeaderDisplayEnabled = flag;
     }
 
     public int getContactNameDisplayOrder() {
         return mDisplayOrder;
     }
 
+    public void setContactNameDisplayOrder(int displayOrder) {
+        mDisplayOrder = displayOrder;
+    }
+
+    public int getSortOrder() {
+        return mSortOrder;
+    }
+
+    public void setSortOrder(int sortOrder) {
+        mSortOrder = sortOrder;
+    }
+
     public void setNameHighlightingEnabled(boolean flag) {
         mNameHighlightingEnabled = flag;
     }
@@ -97,6 +173,39 @@
         return mPhotoLoader;
     }
 
+    public void configureLoader(CursorLoader loader) {
+        Uri uri;
+        if (isSearchMode() || isSearchResultsMode()) {
+            String query = getQueryString();
+            uri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
+                    TextUtils.isEmpty(query) ? "" : Uri.encode(query));
+            loader.setProjection(CONTACTS_SUMMARY_FILTER_PROJECTION);
+            if (!isSearchResultsMode()) {
+                loader.setSelection(Contacts.IN_VISIBLE_GROUP + "=1");
+            }
+        } else {
+            uri = Contacts.CONTENT_URI;
+            loader.setProjection(CONTACTS_SUMMARY_PROJECTION);
+            loader.setSelection(Contacts.IN_VISIBLE_GROUP + "=1");
+        }
+
+        if (isSectionHeaderDisplayEnabled()) {
+            uri = buildSectionIndexerUri(uri);
+        }
+
+        loader.setUri(uri);
+        if (mSortOrder == ContactsContract.Preferences.SORT_ORDER_PRIMARY) {
+            loader.setSortOrder(Contacts.SORT_KEY_PRIMARY);
+        } else {
+            loader.setSortOrder(Contacts.SORT_KEY_ALTERNATIVE);
+        }
+    }
+
+    private static Uri buildSectionIndexerUri(Uri uri) {
+        return uri.buildUpon()
+                .appendQueryParameter(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true").build();
+    }
+
     /*
      * TODO change this method when loaders are introduced.
      */
@@ -109,7 +218,6 @@
     public void moveToPosition(int position) {
         // For side-effect
         getItem(position);
-        DatabaseUtils.dumpCurrentRow(getCursor());
     }
 
     public boolean getHasPhoneNumber() {
diff --git a/src/com/android/contacts/list/ContactEntryListFragment.java b/src/com/android/contacts/list/ContactEntryListFragment.java
index dfeb15e..aaa7f13 100644
--- a/src/com/android/contacts/list/ContactEntryListFragment.java
+++ b/src/com/android/contacts/list/ContactEntryListFragment.java
@@ -25,8 +25,10 @@
 import com.android.contacts.widget.SearchEditText;
 import com.android.contacts.widget.SearchEditText.OnCloseListener;
 
-import android.app.Fragment;
+import android.app.patterns.Loader;
+import android.app.patterns.LoaderManagingFragment;
 import android.content.Context;
+import android.database.Cursor;
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.text.Editable;
@@ -47,14 +49,15 @@
 import android.widget.Filter;
 import android.widget.ListView;
 import android.widget.TextView;
-import android.widget.TextView.OnEditorActionListener;
 import android.widget.AbsListView.OnScrollListener;
 import android.widget.AdapterView.OnItemClickListener;
+import android.widget.TextView.OnEditorActionListener;
 
 /**
  * Common base class for various contact-related list fragments.
  */
-public abstract class ContactEntryListFragment extends Fragment implements OnItemClickListener,
+public abstract class ContactEntryListFragment extends LoaderManagingFragment<Cursor>
+        implements OnItemClickListener,
         OnScrollListener, TextWatcher, OnEditorActionListener, OnCloseListener,
         OnFocusChangeListener, OnTouchListener {
 
@@ -77,10 +80,13 @@
 
     private boolean mLegacyCompatibility;
     private int mDisplayOrder;
+    private int mSortOrder;
+
     private ContextMenuAdapter mContextMenuAdapter;
     private ContactPhotoLoader mPhotoLoader;
     private SearchEditText mSearchEditText;
 
+
     protected abstract View inflateView(LayoutInflater inflater, ViewGroup container);
     protected abstract ContactEntryListAdapter createListAdapter();
     protected abstract void onItemClick(int position, long id);
@@ -89,6 +95,26 @@
         return mAdapter;
     }
 
+    // TODO make abstract
+    @Override
+    protected Loader<Cursor> onCreateLoader(int id, Bundle args) {
+        throw new UnsupportedOperationException();
+    }
+
+    // TODO make abstract
+    @Override
+    protected void onInitializeLoaders() {
+    }
+
+    // TODO make abstract
+    @Override
+    protected void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+        throw new UnsupportedOperationException();
+    }
+
+    protected void reloadData() {
+    }
+
     /**
      * Override to provide logic that dismisses this fragment.
      */
@@ -133,6 +159,9 @@
 
     public void setQueryString(String queryString) {
         mQueryString = queryString;
+        if (mAdapter != null) {
+            mAdapter.setQueryString(queryString);
+        }
     }
 
     public boolean isLegacyCompatibility() {
@@ -143,6 +172,10 @@
         mLegacyCompatibility = flag;
     }
 
+    public int getContactNameDisplayOrder() {
+        return mDisplayOrder;
+    }
+
     public void setContactNameDisplayOrder(int displayOrder) {
         mDisplayOrder = displayOrder;
         if (mAdapter != null) {
@@ -150,6 +183,17 @@
         }
     }
 
+    public int getSortOrder() {
+        return mSortOrder;
+    }
+
+    public void setSortOrder(int sortOrder) {
+        mSortOrder = sortOrder;
+        if (mAdapter != null) {
+            mAdapter.setSortOrder(sortOrder);
+        }
+    }
+
     @Deprecated
     public void setContactsApplicationController(ContactsApplicationController controller) {
         mAppController = controller;
@@ -208,8 +252,6 @@
             mListView.setOnCreateContextMenuListener(mContextMenuAdapter);
         }
 
-        mAdapter.setContactNameDisplayOrder(mDisplayOrder);
-
         configurePinnedHeader();
 
         if (isPhotoLoaderEnabled()) {
@@ -225,7 +267,6 @@
             mSearchEditText.addTextChangedListener(this);
             mSearchEditText.setOnEditorActionListener(this);
             mSearchEditText.setOnCloseListener(this);
-            mAdapter.setQueryString(getQueryString());
         }
 
         if (isSearchResultsMode()) {
@@ -299,9 +340,7 @@
     public void afterTextChanged(Editable s) {
         String query = s.toString().trim();
         setQueryString(query);
-        mAdapter.setQueryString(query);
-        Filter filter = mAdapter.getFilter();
-        filter.filter(query);
+        reloadData();
     }
 
     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
diff --git a/src/com/android/contacts/list/ContactItemListAdapter.java b/src/com/android/contacts/list/ContactItemListAdapter.java
index bd0efa3..35c0fb5 100644
--- a/src/com/android/contacts/list/ContactItemListAdapter.java
+++ b/src/com/android/contacts/list/ContactItemListAdapter.java
@@ -54,7 +54,6 @@
     private boolean mDisplayCallButton = false;
     protected boolean mDisplayAdditionalData = true;
     private int mFrequentSeparatorPos = ListView.INVALID_POSITION;
-    private boolean mSectionHeaderDisplayEnabled;
 
     public ContactItemListAdapter(ContactsListActivity contactsListActivity) {
         super(contactsListActivity);
@@ -82,14 +81,6 @@
         }
     }
 
-    public void setSectionHeaderDisplayEnabled(boolean flag) {
-        mSectionHeaderDisplayEnabled = flag;
-    }
-
-    public boolean isSectionHeaderDisplayEnabled() {
-        return mSectionHeaderDisplayEnabled;
-    }
-
     public void setDisplayPhotos(boolean flag) {
         mDisplayPhotos = flag;
     }
@@ -101,7 +92,7 @@
      */
     @Override
     public void onContentChanged() {
-        CharSequence constraint = mQueryString;
+        CharSequence constraint = getQueryString();
         if (!TextUtils.isEmpty(constraint)) {
             // Reset the filter state then start an async filter operation
             Filter filter = getFilter();
@@ -123,7 +114,7 @@
         }
 
         if (contactsListActivity.mSearchMode) {
-            return TextUtils.isEmpty(mQueryString);
+            return TextUtils.isEmpty(getQueryString());
         } else if ((contactsListActivity.mMode & ContactsListActivity.MODE_MASK_CREATE_NEW) ==
                 ContactsListActivity.MODE_MASK_CREATE_NEW) {
             // This mode mask adds a header and we always want it to show up, even
@@ -205,7 +196,7 @@
             v = convertView;
         }
         bindView(v, getContext(), mCursor);
-        bindSectionHeader(v, realPosition, mSectionHeaderDisplayEnabled);
+        bindSectionHeader(v, realPosition, isSectionHeaderDisplayEnabled());
         return v;
     }
 
@@ -219,7 +210,7 @@
         int count = getRealCount();
 
         if (contactsListActivity.mSearchMode
-                && !TextUtils.isEmpty(mQueryString)) {
+                && !TextUtils.isEmpty(getQueryString())) {
             text = contactsListActivity.getQuantityText(count, R.string.listFoundAllContactsZero,
                     R.plurals.searchFoundContacts);
         } else {