App changes for handling profile DB split.

- Added a custom loader to load in the profile and contact list.
- Made the CursorLoader for ContactEntryListFragment and its
  subclasses pluggable, so the default one could substitute in
  the combined profile-and-contact list loader.
- The photo manager needs some awareness of the profile ID-space,
  since it's doing bulk photo queries using a custom selection.
- Adapted Isaac's change to the section indexer to handle the
  profile being out of consideration when doing the address book
  index query.
- Removed uses of the ALLOW_PROFILE param, since it no longer exists.

Bug 5204577
Bug 5136432
Bug 5140891

Change-Id: I676b4cdeabe87b1b585c6c8df2cde51605777106
diff --git a/src/com/android/contacts/ContactPhotoManager.java b/src/com/android/contacts/ContactPhotoManager.java
index 0f7065d..fd2e6a2 100644
--- a/src/com/android/contacts/ContactPhotoManager.java
+++ b/src/com/android/contacts/ContactPhotoManager.java
@@ -21,6 +21,7 @@
 import com.google.android.collect.Sets;
 
 import android.content.ContentResolver;
+import android.content.ContentUris;
 import android.content.Context;
 import android.content.res.Resources;
 import android.database.Cursor;
@@ -642,7 +643,6 @@
                         ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Directory.DEFAULT))
                         .appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY,
                                 String.valueOf(MAX_PHOTOS_TO_PRELOAD))
-                        .appendQueryParameter(ContactsContract.ALLOW_PROFILE, "1")
                         .build();
                 cursor = mResolver.query(uri, new String[] { Contacts.PHOTO_ID },
                         Contacts.PHOTO_ID + " NOT NULL AND " + Contacts.PHOTO_ID + "!=0",
@@ -698,8 +698,7 @@
 
             Cursor cursor = null;
             try {
-                cursor = mResolver.query(Data.CONTENT_URI.buildUpon()
-                        .appendQueryParameter(ContactsContract.ALLOW_PROFILE, "1").build(),
+                cursor = mResolver.query(Data.CONTENT_URI,
                         COLUMNS,
                         mStringBuilder.toString(),
                         mPhotoIdsAsStrings.toArray(EMPTY_STRING_ARRAY),
@@ -719,9 +718,30 @@
                 }
             }
 
-            // Remaining photos were not found in the database - mark the cache accordingly.
+            // Remaining photos were not found in the contacts database (but might be in profile).
             for (Long id : mPhotoIds) {
-                cacheBitmap(id, null, preloading);
+                if (ContactsContract.isProfileId(id)) {
+                    Cursor profileCursor = null;
+                    try {
+                        profileCursor = mResolver.query(
+                                ContentUris.withAppendedId(Data.CONTENT_URI, id),
+                                COLUMNS, null, null, null);
+                        if (profileCursor != null && profileCursor.moveToFirst()) {
+                            cacheBitmap(profileCursor.getLong(0), profileCursor.getBlob(1),
+                                    preloading);
+                        } else {
+                            // Couldn't load a photo this way either.
+                            cacheBitmap(id, null, preloading);
+                        }
+                    } finally {
+                        if (profileCursor != null) {
+                            profileCursor.close();
+                        }
+                    }
+                } else {
+                    // Not a profile photo and not found - mark the cache accordingly
+                    cacheBitmap(id, null, preloading);
+                }
             }
 
             mMainThreadHandler.sendEmptyMessage(MESSAGE_PHOTOS_LOADED);
diff --git a/src/com/android/contacts/list/ContactEntryListFragment.java b/src/com/android/contacts/list/ContactEntryListFragment.java
index a4163fd..528e246 100644
--- a/src/com/android/contacts/list/ContactEntryListFragment.java
+++ b/src/com/android/contacts/list/ContactEntryListFragment.java
@@ -351,7 +351,7 @@
             mAdapter.configureDirectoryLoader(loader);
             return loader;
         } else {
-            CursorLoader loader = new CursorLoader(mContext, null, null, null, null, null);
+            CursorLoader loader = createCursorLoader();
             long directoryId = args != null && args.containsKey(DIRECTORY_ID_ARG_KEY)
                     ? args.getLong(DIRECTORY_ID_ARG_KEY)
                     : Directory.DEFAULT;
@@ -360,6 +360,10 @@
         }
     }
 
+    public CursorLoader createCursorLoader() {
+        return new CursorLoader(mContext, null, null, null, null, null);
+    }
+
     private void startLoadingDirectoryPartition(int partitionIndex) {
         DirectoryPartition partition = (DirectoryPartition)mAdapter.getPartition(partitionIndex);
         partition.setStatus(DirectoryPartition.STATUS_LOADING);
diff --git a/src/com/android/contacts/list/ContactListAdapter.java b/src/com/android/contacts/list/ContactListAdapter.java
index c057a48..0553909 100644
--- a/src/com/android/contacts/list/ContactListAdapter.java
+++ b/src/com/android/contacts/list/ContactListAdapter.java
@@ -144,11 +144,6 @@
                 .appendQueryParameter(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true").build();
     }
 
-    protected static Uri includeProfileEntry(Uri uri) {
-        return uri.buildUpon()
-                .appendQueryParameter(ContactsContract.ALLOW_PROFILE, "true").build();
-    }
-
     public boolean getHasPhoneNumber(int position) {
         return ((Cursor)getItem(position)).getInt(CONTACT_HAS_PHONE_COLUMN_INDEX) != 0;
     }
diff --git a/src/com/android/contacts/list/ContactsSectionIndexer.java b/src/com/android/contacts/list/ContactsSectionIndexer.java
index 109b8ba..5ecb31c 100644
--- a/src/com/android/contacts/list/ContactsSectionIndexer.java
+++ b/src/com/android/contacts/list/ContactsSectionIndexer.java
@@ -26,9 +26,9 @@
  */
 public class ContactsSectionIndexer implements SectionIndexer {
 
-    private final String[] mSections;
-    private final int[] mPositions;
-    private final int mCount;
+    private String[] mSections;
+    private int[] mPositions;
+    private int mCount;
 
     /**
      * Constructor.
@@ -95,8 +95,25 @@
     }
 
     public void setProfileHeader(String header) {
-        if (mSections != null && mSections.length > 0) {
-            mSections[0] = header;
+        if (mSections != null) {
+            // Don't do anything if the header is already set properly.
+            if (mSections.length > 0 && header.equals(mSections[0])) {
+                return;
+            }
+
+            // Since the section indexer isn't aware of the profile at the top, we need to add a
+            // special section at the top for it and shift everything else down.
+            String[] tempSections = new String[mSections.length + 1];
+            int[] tempPositions = new int[mPositions.length + 1];
+            tempSections[0] = header;
+            tempPositions[0] = 0;
+            for (int i = 1; i <= mPositions.length; i++) {
+                tempSections[i] = mSections[i - 1];
+                tempPositions[i] = mPositions[i - 1] + 1;
+            }
+            mSections = tempSections;
+            mPositions = tempPositions;
+            mCount++;
         }
     }
 }
diff --git a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
index 306e244..9ddc4b7 100644
--- a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
@@ -18,6 +18,7 @@
 import com.android.contacts.R;
 import com.android.contacts.editor.ContactEditorFragment;
 
+import android.content.CursorLoader;
 import android.content.Intent;
 import android.database.Cursor;
 import android.provider.ContactsContract.Contacts;
@@ -56,6 +57,11 @@
     }
 
     @Override
+    public CursorLoader createCursorLoader() {
+        return new ProfileAndContactsLoader(getActivity());
+    }
+
+    @Override
     protected void onItemClick(int position, long id) {
         viewContact(getAdapter().getContactUri(position));
     }
diff --git a/src/com/android/contacts/list/DefaultContactListAdapter.java b/src/com/android/contacts/list/DefaultContactListAdapter.java
index 007af6c..9a3f05e 100644
--- a/src/com/android/contacts/list/DefaultContactListAdapter.java
+++ b/src/com/android/contacts/list/DefaultContactListAdapter.java
@@ -57,6 +57,9 @@
 
     @Override
     public void configureLoader(CursorLoader loader, long directoryId) {
+        if (loader instanceof ProfileAndContactsLoader) {
+            ((ProfileAndContactsLoader) loader).setLoadProfile(shouldIncludeProfile());
+        }
 
         ContactListFilter filter = getFilter();
         if (isSearchMode()) {
@@ -129,11 +132,6 @@
                     .build();
         }
 
-        // Include the user's personal profile.
-        if (shouldIncludeProfile()) {
-            uri = includeProfileEntry(uri);
-        }
-
         loader.setUri(uri);
     }
 
diff --git a/src/com/android/contacts/list/ProfileAndContactsLoader.java b/src/com/android/contacts/list/ProfileAndContactsLoader.java
new file mode 100644
index 0000000..7f85ea6
--- /dev/null
+++ b/src/com/android/contacts/list/ProfileAndContactsLoader.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2011 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.contacts.list;
+
+import com.google.android.collect.Lists;
+
+import android.content.Context;
+import android.content.CursorLoader;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.database.MergeCursor;
+import android.os.Bundle;
+import android.provider.ContactsContract.Profile;
+
+import java.util.List;
+
+/**
+ * A loader for use in the default contact list, which will also query for the user's profile
+ * if configured to do so.
+ */
+public class ProfileAndContactsLoader extends CursorLoader {
+
+    private boolean mLoadProfile;
+    private String[] mProjection;
+
+    public ProfileAndContactsLoader(Context context) {
+        super(context);
+    }
+
+    public void setLoadProfile(boolean flag) {
+        mLoadProfile = flag;
+    }
+
+    public void setProjection(String[] projection) {
+        super.setProjection(projection);
+        mProjection = projection;
+    }
+
+    @Override
+    public Cursor loadInBackground() {
+        // First load the profile, if enabled.
+        List<Cursor> cursors = Lists.newArrayList();
+        if (mLoadProfile) {
+            cursors.add(loadProfile());
+        }
+        final Cursor contactsCursor = super.loadInBackground();
+        cursors.add(contactsCursor);
+        return new MergeCursor(cursors.toArray(new Cursor[cursors.size()])) {
+            @Override
+            public Bundle getExtras() {
+                // Need to get the extras from the contacts cursor.
+                return contactsCursor.getExtras();
+            }
+        };
+    }
+
+    /**
+     * Loads the profile into a MatrixCursor.
+     */
+    private MatrixCursor loadProfile() {
+        Cursor cursor = getContext().getContentResolver().query(Profile.CONTENT_URI, mProjection,
+                null, null, null);
+        try {
+            MatrixCursor matrix = new MatrixCursor(mProjection);
+            Object[] row = new Object[mProjection.length];
+            while (cursor.moveToNext()) {
+                for (int i = 0; i < row.length; i++) {
+                    row[i] = cursor.getString(i);
+                }
+                matrix.addRow(row);
+            }
+            return matrix;
+        } finally {
+            cursor.close();
+        }
+    }
+}