Preventing contacts from joining with Directory entries

Also, moving interactions with PackageManager to the bg thread
Also, skipping a trip to the DB when all we want is the default directories

Change-Id: I3813074af2a01d23d0e90cc2f7905ad1691117a3
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 3c8cd18..cfd3ec9 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1244,4 +1244,7 @@
 
     <!-- Text in the editor to show the structured editor -->
     <string name="edit_structured_editor_button">...</string>
+
+    <!-- The name of the invisible local contact directory -->
+    <string name="local_invisible_directory">Other</string>
 </resources>
diff --git a/src/com/android/contacts/ContactsListActivity.java b/src/com/android/contacts/ContactsListActivity.java
index 037c670..74a7a13 100644
--- a/src/com/android/contacts/ContactsListActivity.java
+++ b/src/com/android/contacts/ContactsListActivity.java
@@ -220,6 +220,7 @@
                 fragment.setSearchMode(mRequest.isSearchMode());
                 fragment.setSearchResultsMode(mRequest.isSearchResultsMode());
                 fragment.setQueryString(mRequest.getQueryString());
+                fragment.setDirectorySearchEnabled(mRequest.isDirectorySearchEnabled());
                 mListFragment = fragment;
                 break;
             }
@@ -280,6 +281,7 @@
                 fragment.setLegacyCompatibilityMode(mRequest.isLegacyCompatibilityMode());
                 fragment.setSearchMode(mRequest.isSearchMode());
                 fragment.setQueryString(mRequest.getQueryString());
+                fragment.setDirectorySearchEnabled(mRequest.isDirectorySearchEnabled());
                 fragment.setShortcutRequested(true);
                 mListFragment = fragment;
                 break;
diff --git a/src/com/android/contacts/list/ContactEntryListAdapter.java b/src/com/android/contacts/list/ContactEntryListAdapter.java
index b99c0d1..3f42f50 100644
--- a/src/com/android/contacts/list/ContactEntryListAdapter.java
+++ b/src/com/android/contacts/list/ContactEntryListAdapter.java
@@ -23,14 +23,11 @@
 
 import android.content.Context;
 import android.content.CursorLoader;
-import android.content.pm.PackageManager;
 import android.database.Cursor;
-import android.net.Uri;
 import android.os.Bundle;
 import android.provider.ContactsContract.ContactCounts;
 import android.provider.ContactsContract.Directory;
 import android.text.TextUtils;
-import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -46,23 +43,6 @@
 
     private static final String TAG = "ContactEntryListAdapter";
 
-    private static final class DirectoryQuery {
-        public static final Uri URI = Directory.CONTENT_URI;
-        public static final String ORDER_BY = Directory._ID;
-
-        public static final String[] PROJECTION = {
-            Directory._ID,
-            Directory.PACKAGE_NAME,
-            Directory.TYPE_RESOURCE_ID,
-            Directory.DISPLAY_NAME,
-        };
-
-        public static final int ID = 0;
-        public static final int PACKAGE_NAME = 1;
-        public static final int TYPE_RESOURCE_ID = 2;
-        public static final int DISPLAY_NAME = 3;
-    }
-
     /**
      * The animation is used here to allocate animated name text views.
      */
@@ -77,6 +57,7 @@
     private String mQueryString;
     private boolean mSearchMode;
     private boolean mSearchResultsMode;
+    private boolean mDirectorySearchEnabled;
 
     private boolean mLoading = true;
     private boolean mEmptyListEnabled = true;
@@ -159,6 +140,14 @@
         mQueryString = queryString;
     }
 
+    public boolean isDirectorySearchEnabled() {
+        return mDirectorySearchEnabled;
+    }
+
+    public void setDirectorySearchEnabled(boolean flag) {
+        mDirectorySearchEnabled = flag;
+    }
+
     public int getContactNameDisplayOrder() {
         return mDisplayOrder;
     }
@@ -215,10 +204,8 @@
         mEmptyListEnabled = flag;
     }
 
-    public void configureDirectoryLoader(CursorLoader loader) {
-        loader.setUri(DirectoryQuery.URI);
-        loader.setProjection(DirectoryQuery.PROJECTION);
-        loader.setSortOrder(DirectoryQuery.ORDER_BY);
+    public void configureDirectoryLoader(DirectoryListLoader loader) {
+        loader.setDirectorySearchEnabled(mDirectorySearchEnabled);
     }
 
     /**
@@ -228,14 +215,21 @@
     public void changeDirectories(Cursor cursor) {
         HashSet<Long> directoryIds = new HashSet<Long>();
 
+        int idColumnIndex = cursor.getColumnIndex(Directory._ID);
+        int directoryTypeColumnIndex = cursor.getColumnIndex(DirectoryListLoader.DIRECTORY_TYPE);
+        int displayNameColumnIndex = cursor.getColumnIndex(Directory.DISPLAY_NAME);
+
         // TODO preserve the order of partition to match those of the cursor
         // Phase I: add new directories
         try {
             while (cursor.moveToNext()) {
-                long id = cursor.getLong(DirectoryQuery.ID);
+                long id = cursor.getLong(idColumnIndex);
                 directoryIds.add(id);
                 if (getPartitionByDirectoryId(id) == -1) {
-                    DirectoryPartition partition = createDirectoryPartition(cursor);
+                    DirectoryPartition partition = new DirectoryPartition(false, true);
+                    partition.setDirectoryId(id);
+                    partition.setDirectoryType(cursor.getString(directoryTypeColumnIndex));
+                    partition.setDisplayName(cursor.getString(displayNameColumnIndex));
                     addPartition(partition);
                 }
             }
@@ -259,25 +253,6 @@
         notifyDataSetChanged();
     }
 
-    private DirectoryPartition createDirectoryPartition(Cursor cursor) {
-        PackageManager pm = getContext().getPackageManager();
-        DirectoryPartition partition = new DirectoryPartition(false, true);
-        partition.setDirectoryId(cursor.getLong(DirectoryQuery.ID));
-        String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
-        int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
-        if (!TextUtils.isEmpty(packageName) && typeResourceId != 0) {
-            // TODO: should this be done on a background thread?
-            try {
-                partition.setDirectoryType(pm.getResourcesForApplication(packageName)
-                        .getString(typeResourceId));
-            } catch (Exception e) {
-                Log.e(TAG, "Cannot obtain directory type from package: " + packageName);
-            }
-        }
-        partition.setDisplayName(cursor.getString(DirectoryQuery.DISPLAY_NAME));
-        return partition;
-    }
-
     @Override
     public void changeCursor(int partitionIndex, Cursor cursor) {
         Partition partition = getPartition(partitionIndex);
diff --git a/src/com/android/contacts/list/ContactEntryListFragment.java b/src/com/android/contacts/list/ContactEntryListFragment.java
index b111e12..9e208bc 100644
--- a/src/com/android/contacts/list/ContactEntryListFragment.java
+++ b/src/com/android/contacts/list/ContactEntryListFragment.java
@@ -89,6 +89,7 @@
     private boolean mSearchResultsMode;
     private boolean mAizyEnabled;
     private String mQueryString;
+    private boolean mDirectorySearchEnabled;
 
     private T mAdapter;
     private View mView;
@@ -210,16 +211,18 @@
 
     @Override
     protected Loader<Cursor> onCreateLoader(int id, Bundle args) {
-        CursorLoader loader = new CursorLoader(getActivity(), null, null, null, null, null);
         if (id == DIRECTORY_LOADER_ID) {
+            DirectoryListLoader loader = new DirectoryListLoader(getActivity());
             mAdapter.configureDirectoryLoader(loader);
+            return loader;
         } else {
+            CursorLoader loader = new CursorLoader(getActivity(), null, null, null, null, null);
             long directoryId = args != null && args.containsKey(DIRECTORY_ID_ARG_KEY)
                     ? args.getLong(DIRECTORY_ID_ARG_KEY)
                     : Directory.DEFAULT;
             mAdapter.configureLoader(loader, directoryId);
+            return loader;
         }
-        return loader;
     }
 
     private void startLoadingDirectoryPartition(int partitionIndex) {
@@ -408,6 +411,14 @@
         }
     }
 
+    public boolean isDirectorySearchEnabled() {
+        return mDirectorySearchEnabled;
+    }
+
+    public void setDirectorySearchEnabled(boolean flag) {
+        mDirectorySearchEnabled = flag;
+    }
+
     public boolean isLegacyCompatibilityMode() {
         return mLegacyCompatibility;
     }
@@ -559,6 +570,7 @@
         }
 
         mAdapter.setQueryString(mQueryString);
+        mAdapter.setDirectorySearchEnabled(mDirectorySearchEnabled);
         mAdapter.setPinnedPartitionHeadersEnabled(mSearchMode);
         mAdapter.setContactNameDisplayOrder(mDisplayOrder);
         mAdapter.setSortOrder(mSortOrder);
diff --git a/src/com/android/contacts/list/ContactsRequest.java b/src/com/android/contacts/list/ContactsRequest.java
index 6f2e2e4..5bda8e2 100644
--- a/src/com/android/contacts/list/ContactsRequest.java
+++ b/src/com/android/contacts/list/ContactsRequest.java
@@ -80,6 +80,7 @@
     private boolean mDisplayOnlyVisible;
     private String mGroupName;
     private boolean mLegacyCompatibilityMode;
+    private boolean mDirectorySearchEnabled = true;
 
     /**
      * Copies all fields.
@@ -96,6 +97,7 @@
         mDisplayOnlyVisible = request.mDisplayOnlyVisible;
         mGroupName = request.mGroupName;
         mLegacyCompatibilityMode = request.mLegacyCompatibilityMode;
+        mDirectorySearchEnabled = request.mDirectorySearchEnabled;
     }
 
     public static Parcelable.Creator<ContactsRequest> CREATOR = new Creator<ContactsRequest>() {
@@ -117,6 +119,7 @@
             request.mDisplayOnlyVisible = source.readInt() != 0;
             request.mGroupName = source.readString();
             request.mLegacyCompatibilityMode  = source.readInt() != 0;
+            request.mDirectorySearchEnabled = source.readInt() != 0;
             return request;
         }
     };
@@ -133,6 +136,7 @@
         dest.writeInt(mDisplayOnlyVisible ? 1 : 0);
         dest.writeString(mGroupName);
         dest.writeInt(mLegacyCompatibilityMode ? 1 : 0);
+        dest.writeInt(mDirectorySearchEnabled ? 1 : 0);
     }
 
     public int describeContents() {
@@ -226,4 +230,16 @@
     public void setLegacyCompatibilityMode(boolean flag) {
         mLegacyCompatibilityMode = flag;
     }
+
+    /**
+     * Determines whether this search request should include directories or
+     * is limited to local contacts only.
+     */
+    public boolean isDirectorySearchEnabled() {
+        return mDirectorySearchEnabled;
+    }
+
+    public void setDirectorySearchEnabled(boolean flag) {
+        mDirectorySearchEnabled = flag;
+    }
 }
diff --git a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
index 24b48d3..dfc4c26 100644
--- a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
@@ -163,19 +163,19 @@
         }
     }
 
-    public void setEditMode(boolean flag) {
-        mEditMode = flag;
-    }
-
     public boolean isEditMode() {
         return mEditMode;
     }
 
-    public void setCreateContactEnabled(boolean flag) {
-        this.mCreateContactEnabled = flag;
+    public void setEditMode(boolean flag) {
+        mEditMode = flag;
     }
 
     public boolean isCreateContactEnabled() {
         return mCreateContactEnabled;
     }
+
+    public void setCreateContactEnabled(boolean flag) {
+        this.mCreateContactEnabled = flag;
+    }
 }
diff --git a/src/com/android/contacts/list/DirectoryListLoader.java b/src/com/android/contacts/list/DirectoryListLoader.java
new file mode 100644
index 0000000..b8cc2c9
--- /dev/null
+++ b/src/com/android/contacts/list/DirectoryListLoader.java
@@ -0,0 +1,156 @@
+/*
+ * 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.contacts.list;
+
+import com.android.contacts.R;
+
+import android.content.AsyncTaskLoader;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.ContactsContract.Directory;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * A specialized loader for the list of directories, see {@link Directory}.
+ */
+public class DirectoryListLoader extends AsyncTaskLoader<Cursor> {
+
+    private static final String TAG = "ContactEntryListAdapter";
+
+    private static final class DirectoryQuery {
+        public static final Uri URI = Directory.CONTENT_URI;
+        public static final String ORDER_BY = Directory._ID;
+
+        public static final String[] PROJECTION = {
+            Directory._ID,
+            Directory.PACKAGE_NAME,
+            Directory.TYPE_RESOURCE_ID,
+            Directory.DISPLAY_NAME,
+        };
+
+        public static final int ID = 0;
+        public static final int PACKAGE_NAME = 1;
+        public static final int TYPE_RESOURCE_ID = 2;
+        public static final int DISPLAY_NAME = 3;
+    }
+
+    public static final String DIRECTORY_TYPE = "directoryType";
+
+    private static final String[] RESULT_PROJECTION = {
+        Directory._ID,
+        DIRECTORY_TYPE,
+        Directory.DISPLAY_NAME,
+    };
+
+    private final ContentObserver mObserver = new ContentObserver(new Handler()) {
+        @Override
+        public void onChange(boolean selfChange) {
+            forceLoad();
+        }
+    };
+
+    private boolean mDirectorySearchEnabled;
+
+    private MatrixCursor mDefaultDirectoryList;
+
+    public DirectoryListLoader(Context context) {
+        super(context);
+    }
+
+    public void setDirectorySearchEnabled(boolean flag) {
+        mDirectorySearchEnabled = flag;
+    }
+
+    @Override
+    public void startLoading() {
+        getContext().getContentResolver().
+                registerContentObserver(Directory.CONTENT_URI, false, mObserver);
+        forceLoad();
+    }
+
+    @Override
+    public void stopLoading() {
+        getContext().getContentResolver().unregisterContentObserver(mObserver);
+    }
+
+    @Override
+    public Cursor loadInBackground() {
+        if (mDirectorySearchEnabled) {
+            return loadDirectories();
+        } else {
+            return getDefaultDirectories();
+        }
+    }
+
+    private Cursor loadDirectories() {
+        MatrixCursor result = new MatrixCursor(RESULT_PROJECTION);
+        Context context = getContext();
+        PackageManager pm = context.getPackageManager();
+        Cursor cursor = context.getContentResolver().query(DirectoryQuery.URI,
+                DirectoryQuery.PROJECTION, null, null, DirectoryQuery.ORDER_BY);
+        try {
+            while(cursor.moveToNext()) {
+                Object[] row = new Object[RESULT_PROJECTION.length];
+                long directoryId = cursor.getLong(DirectoryQuery.ID);
+                String directoryType = null;
+
+                String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
+                int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
+                if (!TextUtils.isEmpty(packageName) && typeResourceId != 0) {
+                    try {
+                        directoryType = pm.getResourcesForApplication(packageName)
+                                .getString(typeResourceId);
+                    } catch (Exception e) {
+                        Log.e(TAG, "Cannot obtain directory type from package: " + packageName);
+                    }
+                }
+                String displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
+                result.addRow(new Object[]{directoryId, directoryType, displayName});
+            }
+        } finally {
+            cursor.close();
+        }
+        return result;
+    }
+
+    private Cursor getDefaultDirectories() {
+        if (mDefaultDirectoryList == null) {
+            mDefaultDirectoryList = new MatrixCursor(RESULT_PROJECTION);
+            mDefaultDirectoryList.addRow(new Object[] {
+                    Directory.DEFAULT,
+                    getContext().getString(R.string.contactsList),
+                    null
+            });
+            mDefaultDirectoryList.addRow(new Object[] {
+                    Directory.LOCAL_INVISIBLE,
+                    getContext().getString(R.string.local_invisible_directory),
+                    null
+            });
+        }
+        return mDefaultDirectoryList;
+    }
+
+    @Override
+    public void destroy() {
+        stopLoading();
+    }
+}
diff --git a/src/com/android/contacts/list/JoinContactListFragment.java b/src/com/android/contacts/list/JoinContactListFragment.java
index 74ff844..8c6be47 100644
--- a/src/com/android/contacts/list/JoinContactListFragment.java
+++ b/src/com/android/contacts/list/JoinContactListFragment.java
@@ -154,6 +154,7 @@
     public void startSearch(String initialQuery) {
         ContactsRequest request = new ContactsRequest();
         request.setActionCode(ContactsRequest.ACTION_PICK_CONTACT);
+        request.setDirectorySearchEnabled(false);
         ContactsSearchManager.startSearchForResult(getActivity(), initialQuery,
                 ACTIVITY_REQUEST_CODE_PICKER, request);
     }