Splitting off the Join Contact mode.

This is not a strategic refactoring, just a step
toward splitting Contacts List into multiple
classes.

Change-Id: I6ec5472ef31e87f62d7d62e31b4f413627a9b1f8
diff --git a/src/com/android/contacts/JoinContactActivity.java b/src/com/android/contacts/JoinContactActivity.java
new file mode 100644
index 0000000..85a050c
--- /dev/null
+++ b/src/com/android/contacts/JoinContactActivity.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2007 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;
+
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.net.Uri.Builder;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Contacts.AggregationSuggestions;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+/**
+ * An activity that shows a list of contacts that can be joined with the target contact.
+ */
+public class JoinContactActivity extends ContactsListActivity {
+
+    private static final String TAG = "JoinContactActivity";
+
+    /**
+     * The action for the join contact activity.
+     * <p>
+     * Input: extra field {@link #EXTRA_TARGET_CONTACT_ID} is the aggregate ID.
+     * TODO: move to {@link ContactsContract}.
+     */
+    public static final String JOIN_CONTACT = "com.android.contacts.action.JOIN_CONTACT";
+
+    /**
+     * Used with {@link #JOIN_CONTACT} to give it the target for aggregation.
+     * <p>
+     * Type: LONG
+     */
+    public static final String EXTRA_TARGET_CONTACT_ID = "com.android.contacts.action.CONTACT_ID";
+
+    /** Maximum number of suggestions shown for joining aggregates */
+    private static final int MAX_SUGGESTIONS = 4;
+
+    private long mTargetContactId;
+
+    /**
+     * Determines whether we display a list item with the label
+     * "Show all contacts" or actually show all contacts
+     */
+    private boolean mJoinModeShowAllContacts;
+
+    /**
+     * The ID of the special item described above.
+     */
+    private static final long JOIN_MODE_SHOW_ALL_CONTACTS_ID = -2;
+
+    private boolean mLoadingJoinSuggestions;
+
+    private JoinContactListAdapter mAdapter;
+
+    @Override
+    protected void resolveIntent(Intent intent) {
+        mMode = MODE_PICK_CONTACT;
+        mTargetContactId = intent.getLongExtra(EXTRA_TARGET_CONTACT_ID, -1);
+        if (mTargetContactId == -1) {
+            Log.e(TAG, "Intent " + intent.getAction() + " is missing required extra: "
+                    + EXTRA_TARGET_CONTACT_ID);
+            setResult(RESULT_CANCELED);
+            finish();
+        }
+    }
+
+    @Override
+    public void initContentView() {
+        setContentView(R.layout.contacts_list_content_join);
+        TextView blurbView = (TextView)findViewById(R.id.join_contact_blurb);
+
+        String blurb = getString(R.string.blurbJoinContactDataWith,
+                getContactDisplayName(mTargetContactId));
+        blurbView.setText(blurb);
+        mJoinModeShowAllContacts = true;
+        mAdapter = new JoinContactListAdapter(this);
+        setupListView(mAdapter);
+    }
+
+    @Override
+    protected void onListItemClick(int position, long id) {
+        if (id == JOIN_MODE_SHOW_ALL_CONTACTS_ID) {
+            mJoinModeShowAllContacts = false;
+            startQuery();
+        } else {
+            final Uri uri = getSelectedUri(position);
+            returnPickerResult(null, null, uri);
+        }
+    }
+
+    @Override
+    protected Uri getUriToQuery() {
+        return getJoinSuggestionsUri(null);
+    }
+
+    /*
+     * TODO: move to a background thread.
+     */
+    private String getContactDisplayName(long contactId) {
+        String contactName = null;
+        Cursor c = getContentResolver().query(
+                ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
+                new String[] {Contacts.DISPLAY_NAME}, null, null, null);
+        try {
+            if (c != null && c.moveToFirst()) {
+                contactName = c.getString(0);
+            }
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+
+        if (contactName == null) {
+            contactName = "";
+        }
+
+        return contactName;
+    }
+
+    private Uri getJoinSuggestionsUri(String filter) {
+        Builder builder = Contacts.CONTENT_URI.buildUpon();
+        builder.appendEncodedPath(String.valueOf(mTargetContactId));
+        builder.appendEncodedPath(AggregationSuggestions.CONTENT_DIRECTORY);
+        if (!TextUtils.isEmpty(filter)) {
+            builder.appendEncodedPath(Uri.encode(filter));
+        }
+        builder.appendQueryParameter("limit", String.valueOf(MAX_SUGGESTIONS));
+        return builder.build();
+    }
+
+    @Override
+    Cursor doFilter(String filter) {
+        throw new UnsupportedOperationException();
+    }
+
+    private Cursor getShowAllContactsLabelCursor(String[] projection) {
+        MatrixCursor matrixCursor = new MatrixCursor(projection);
+        Object[] row = new Object[projection.length];
+        // The only columns we care about is the id
+        row[SUMMARY_ID_COLUMN_INDEX] = JOIN_MODE_SHOW_ALL_CONTACTS_ID;
+        matrixCursor.addRow(row);
+        return matrixCursor;
+    }
+
+    @Override
+    protected void startQuery(Uri uri, String[] projection) {
+        mLoadingJoinSuggestions = true;
+        startQuery(uri, projection, null, null, null);
+    }
+
+    @Override
+    protected void onQueryComplete(Cursor cursor) {
+        // Whenever we get a suggestions cursor, we need to immediately kick off
+        // another query for the complete list of contacts
+        if (cursor != null && mLoadingJoinSuggestions) {
+            mLoadingJoinSuggestions = false;
+            if (cursor.getCount() > 0) {
+                mAdapter.setSuggestionsCursor(cursor);
+            } else {
+                cursor.close();
+                mAdapter.setSuggestionsCursor(null);
+            }
+
+            if (mAdapter.mSuggestionsCursorCount == 0
+                    || !mJoinModeShowAllContacts) {
+                startQuery(getContactFilterUri(getTextFilter()),
+                        CONTACTS_SUMMARY_PROJECTION,
+                        Contacts._ID + " != " + mTargetContactId
+                                + " AND " + ContactsContract.Contacts.IN_VISIBLE_GROUP + "=1", null,
+                        getSortOrder(CONTACTS_SUMMARY_PROJECTION));
+                return;
+            }
+
+            cursor = getShowAllContactsLabelCursor(CONTACTS_SUMMARY_PROJECTION);
+        }
+
+        super.onQueryComplete(cursor);
+    }
+
+    @Override
+    protected void setEmptyText() {
+        return;
+    }
+
+    private class JoinContactListAdapter extends ContactItemListAdapter {
+        Cursor mSuggestionsCursor;
+        int mSuggestionsCursorCount;
+
+        public JoinContactListAdapter(Context context) {
+            super(context);
+        }
+
+        public void setSuggestionsCursor(Cursor cursor) {
+            if (mSuggestionsCursor != null) {
+                mSuggestionsCursor.close();
+            }
+            mSuggestionsCursor = cursor;
+            mSuggestionsCursorCount = cursor == null ? 0 : cursor.getCount();
+        }
+
+        private boolean isShowAllContactsItemPosition(int position) {
+            return mJoinModeShowAllContacts
+                    && mSuggestionsCursorCount != 0 && position == mSuggestionsCursorCount + 2;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            if (!mDataValid) {
+                throw new IllegalStateException(
+                        "this should only be called when the cursor is valid");
+            }
+
+            if (isShowAllContactsItemPosition(position)) {
+                return getLayoutInflater().
+                        inflate(R.layout.contacts_list_show_all_item, parent, false);
+            }
+
+            // Handle the separator specially
+            int separatorId = getSeparatorId(position);
+            if (separatorId != 0) {
+                TextView view = (TextView) getLayoutInflater().
+                        inflate(R.layout.list_separator, parent, false);
+                view.setText(separatorId);
+                return view;
+            }
+
+            boolean showingSuggestion;
+            Cursor cursor;
+            if (mSuggestionsCursorCount != 0 && position < mSuggestionsCursorCount + 2) {
+                showingSuggestion = true;
+                cursor = mSuggestionsCursor;
+            } else {
+                showingSuggestion = false;
+                cursor = mCursor;
+            }
+
+            int realPosition = getRealPosition(position);
+            if (!cursor.moveToPosition(realPosition)) {
+                throw new IllegalStateException("couldn't move cursor to position " + position);
+            }
+
+            boolean newView;
+            View v;
+            if (convertView == null || convertView.getTag() == null) {
+                newView = true;
+                v = newView(mContext, cursor, parent);
+            } else {
+                newView = false;
+                v = convertView;
+            }
+            bindView(v, mContext, cursor);
+            bindSectionHeader(v, realPosition, !showingSuggestion);
+            return v;
+        }
+
+        @Override
+        public void changeCursor(Cursor cursor) {
+            if (cursor == null) {
+                mAdapter.setSuggestionsCursor(null);
+            }
+
+            super.changeCursor(cursor);
+        }
+        @Override
+        public int getItemViewType(int position) {
+            if (isShowAllContactsItemPosition(position)) {
+                return IGNORE_ITEM_VIEW_TYPE;
+            }
+
+            return super.getItemViewType(position);
+        }
+
+        private int getSeparatorId(int position) {
+            if (mSuggestionsCursorCount != 0) {
+                if (position == 0) {
+                    return R.string.separatorJoinAggregateSuggestions;
+                } else if (position == mSuggestionsCursorCount + 1) {
+                    return R.string.separatorJoinAggregateAll;
+                }
+            }
+            return 0;
+        }
+
+        @Override
+        public boolean areAllItemsEnabled() {
+            return super.areAllItemsEnabled() && mSuggestionsCursorCount == 0;
+        }
+
+        @Override
+        public boolean isEnabled(int position) {
+            if (position == 0) {
+                return false;
+            }
+
+            if (mSuggestionsCursorCount > 0) {
+                return position != 0 && position != mSuggestionsCursorCount + 1;
+            }
+            return true;
+        }
+
+        @Override
+        public int getCount() {
+            if (!mDataValid) {
+                return 0;
+            }
+            int superCount = super.getCount();
+            if (mSuggestionsCursorCount != 0) {
+                // When showing suggestions, we have 2 additional list items: the "Suggestions"
+                // and "All contacts" headers.
+                return mSuggestionsCursorCount + superCount + 2;
+            }
+            return superCount;
+        }
+
+        private int getRealPosition(int pos) {
+            if (mSuggestionsCursorCount != 0) {
+                // When showing suggestions, we have 2 additional list items: the "Suggestions"
+                // and "All contacts" separators.
+                if (pos < mSuggestionsCursorCount + 2) {
+                    // We are in the upper partition (Suggestions). Adjusting for the "Suggestions"
+                    // separator.
+                    return pos - 1;
+                } else {
+                    // We are in the lower partition (All contacts). Adjusting for the size
+                    // of the upper partition plus the two separators.
+                    return pos - mSuggestionsCursorCount - 2;
+                }
+            } else {
+                // No separator, identity map
+                return pos;
+            }
+        }
+
+        @Override
+        public Object getItem(int pos) {
+            if (mSuggestionsCursorCount != 0 && pos <= mSuggestionsCursorCount) {
+                mSuggestionsCursor.moveToPosition(getRealPosition(pos));
+                return mSuggestionsCursor;
+            } else {
+                int realPosition = getRealPosition(pos);
+                if (realPosition < 0) {
+                    return null;
+                }
+                return super.getItem(realPosition);
+            }
+        }
+
+        @Override
+        public long getItemId(int pos) {
+            if (mSuggestionsCursorCount != 0 && pos < mSuggestionsCursorCount + 2) {
+                if (mSuggestionsCursor.moveToPosition(pos - 1)) {
+                    return mSuggestionsCursor.getLong(mRowIDColumn);
+                } else {
+                    return 0;
+                }
+            }
+            int realPosition = getRealPosition(pos);
+            if (realPosition < 0) {
+                return 0;
+            }
+            return super.getItemId(realPosition);
+        }
+    }
+}