Merge "Add suggestion card for quick contact (P2)" into ub-contactsdialer-a-dev
diff --git a/res/layout/quickcontact_content.xml b/res/layout/quickcontact_content.xml
index 494b93b..9c4e644 100644
--- a/res/layout/quickcontact_content.xml
+++ b/res/layout/quickcontact_content.xml
@@ -54,6 +54,8 @@
             android:visibility="gone"
             cardview:cardCornerRadius="@dimen/expanding_entry_card_card_corner_radius" />
 
+        <include layout="@layout/quickcontact_suggestion_card" />
+
     </LinearLayout>
 
 </com.android.contacts.widget.TouchlessScrollView>
\ No newline at end of file
diff --git a/res/layout/quickcontact_suggestion_card.xml b/res/layout/quickcontact_suggestion_card.xml
new file mode 100644
index 0000000..45e316b
--- /dev/null
+++ b/res/layout/quickcontact_suggestion_card.xml
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+
+<!--
+  Layout for the suggestion card in QuickContact.
+-->
+<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:card_view="http://schemas.android.com/apk/res-auto"
+    style="@style/ExpandingEntryCardStyle"
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent"
+    android:visibility="gone"
+    android:id="@+id/suggestion_card_view">
+
+    <LinearLayout
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:layout_marginTop="@dimen/quickcontact_suggestion_card_layout_margin"
+            android:layout_marginBottom="@dimen/quickcontact_suggestion_card_layout_margin"
+            android:orientation="horizontal">
+
+            <ImageView
+                android:id="@+id/suggestion_icon"
+                android:layout_width="@dimen/quickcontact_suggestion_card_icon_height"
+                android:layout_height="@dimen/quickcontact_suggestion_card_icon_width"
+                android:layout_gravity="center_vertical"
+                android:layout_marginStart="@dimen/quickcontact_suggestion_card_image_spacing"
+                android:scaleType="fitCenter" />
+
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:orientation="vertical"
+                android:layout_gravity="center_vertical"
+                android:layout_weight="1"
+                android:layout_toEndOf="@+id/line_vertical_separator"
+                android:layout_marginStart="@dimen/expanding_entry_card_item_image_spacing"
+                android:layout_marginEnd="@dimen/expanding_entry_card_item_image_spacing">
+
+                <TextView
+                    android:id="@+id/suggestion_for_name"
+                    android:textSize="@dimen/expanding_entry_card_title_text_size"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:textColor="@color/quickcontact_entry_header_text_color"
+                    android:singleLine="true"
+                    android:ellipsize="end"
+                    android:textAlignment="viewStart"/>
+
+                <TextView
+                    android:id="@+id/suggestion_number"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:textAlignment="viewStart"
+                    android:singleLine="true"
+                    android:ellipsize="end"
+                    android:textColor="@color/quickcontact_entry_sub_header_text_color" />
+
+                <TextView
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:id="@+id/suggestion_summary"
+                    android:textAlignment="viewStart"
+                    android:singleLine="true"
+                    android:ellipsize="end"
+                    android:textColor="@color/quickcontact_entry_sub_header_text_color" />
+            </LinearLayout>
+
+            <View
+                android:id="@+id/line_vertical_separator"
+                android:layout_width="@dimen/divider_line_height"
+                android:layout_height="match_parent"
+                android:layout_toEndOf="@+id/expand_suggestion_button"
+                android:background="@color/divider_line_color_light"/>
+
+            <ImageView
+                android:id="@+id/expand_suggestion_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical|end"
+                android:layout_alignParentEnd="true"
+                android:paddingStart="@dimen/editor_round_button_padding_left"
+                android:paddingEnd="@dimen/editor_round_button_padding_right"
+                android:paddingTop="@dimen/editor_round_button_padding_top"
+                android:paddingBottom="@dimen/editor_round_button_padding_bottom"/>
+
+        </LinearLayout>
+
+        <View
+            android:id="@+id/title_separator"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/divider_line_height"
+            android:paddingTop="@dimen/editor_round_button_padding_top"
+            android:background="@color/divider_line_color_light"/>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/suggestion_list"
+            android:animateLayoutChanges="true"
+            android:orientation="vertical" />
+
+        <View
+            android:id="@+id/title_separator2"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/divider_line_height"
+            android:background="@color/divider_line_color_light"
+            android:visibility="gone"/>
+
+        <Button
+            android:id="@+id/merge_button"
+            style="?android:attr/buttonBarButtonStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/quickcontact_suggestion_merge_button"
+            android:textColor="@color/quickcontact_entry_sub_header_text_color"
+            android:paddingStart="@dimen/quickcontact_suggestion_card_image_spacing"/>
+    </LinearLayout>
+</android.support.v7.widget.CardView>
\ No newline at end of file
diff --git a/res/layout/quickcontact_suggestion_contact_item.xml b/res/layout/quickcontact_suggestion_contact_item.xml
new file mode 100644
index 0000000..76792f8
--- /dev/null
+++ b/res/layout/quickcontact_suggestion_contact_item.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginTop="@dimen/quickcontact_suggestion_card_layout_margin"
+    android:layout_marginBottom="@dimen/quickcontact_suggestion_card_layout_margin">
+
+    <ImageView
+        android:id="@+id/aggregation_suggestion_photo"
+        android:layout_width="@dimen/quickcontact_suggestion_card_icon_height"
+        android:layout_height="@dimen/quickcontact_suggestion_card_icon_width"
+        android:layout_marginStart="@dimen/quickcontact_suggestion_card_image_spacing"
+        android:scaleType="fitCenter"
+        android:layout_gravity="center_vertical" />
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/expanding_entry_card_item_image_spacing"
+        android:layout_weight="1"
+        android:orientation="vertical"
+        android:layout_gravity="center_vertical">
+
+        <TextView
+            android:id="@+id/aggregation_suggestion_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="@dimen/expanding_entry_card_title_text_size"
+            android:textColor="@color/quickcontact_entry_sub_header_text_color"
+            android:layout_marginTop="@dimen/quickcontact_suggestion_card_layout_margin"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:textAppearance="?android:attr/textAppearanceMedium" />
+
+        <TextView
+            android:id="@+id/aggregation_suggestion_account_type"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:layout_marginBottom="@dimen/quickcontact_suggestion_card_layout_margin"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:textColor="@color/quickcontact_entry_sub_header_text_color" />
+
+    </LinearLayout>
+
+    <CheckBox
+        android:id="@+id/suggestion_checkbox"
+        android:layout_gravity="center_vertical|end"
+        android:layout_alignParentEnd="true"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:shadowColor="@color/divider_line_color_light"
+        android:layout_marginEnd="@dimen/quickcontact_suggestion_card_checkbox_right_margin"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index c57127f..a5187d2 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -228,6 +228,12 @@
     <dimen name="expanding_entry_card_item_header_only_margin_bottom">2dp</dimen>
     <dimen name="expanding_entry_card_item_no_icon_margin_top">6dp</dimen>
 
+    <dimen name="quickcontact_suggestion_card_icon_height">24dp</dimen>
+    <dimen name="quickcontact_suggestion_card_icon_width">24dp</dimen>
+    <dimen name="quickcontact_suggestion_card_image_spacing">20dp</dimen>
+    <dimen name="quickcontact_suggestion_card_layout_margin">8dp</dimen>
+    <dimen name="quickcontact_suggestion_card_checkbox_right_margin">16dp</dimen>
+
     <!-- The width the that the tabs occupy in the ActionBar when in landscape mode.
          426dp is the height of a "small" screen. We should leave 240dp for
          the title and menu items -->
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 020b999..1104a24 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -773,4 +773,22 @@
 
     <!-- Quick contact display name with phonetic name -->
     <string name="quick_contact_display_name_with_phonetic"><xliff:g id="display_name">%s</xliff:g> (<xliff:g id="phonetic_name">%s</xliff:g>)</string>
+
+    <!-- Button used in quick contact suggestion card to merge selected contacts. [CHAR LIMIT=30]-->
+    <string name="quickcontact_suggestion_merge_button">Merge</string>
+
+    <!-- Suggestions number in quick contact suggestion card [CHAR LIMIT=30] -->
+    <plurals name="quickcontact_suggestions_number">
+        <item quantity="one">1 suggested contact</item>
+        <item quantity="other"><xliff:g id="count">%d</xliff:g> suggested contacts</item>
+    </plurals>
+
+    <!-- Account type number for suggestions in quick contact suggestion card [CHAR LIMIT=30]-->
+    <plurals name="quickcontact_suggestion_account_type_number">
+        <item quantity="one"></item>
+        <item quantity="other">(<xliff:g id="count">%d</xliff:g>)</item>
+    </plurals>
+
+    <!-- Account type with number in quick contact suggestion card [CHAR LIMIT=30]-->
+    <string name="quickcontact_suggestion_account_type"><xliff:g id="account_type">%s</xliff:g><xliff:g id="account_type_number">%s</xliff:g></string>
 </resources>
diff --git a/src/com/android/contacts/editor/AggregationSuggestionEngine.java b/src/com/android/contacts/editor/AggregationSuggestionEngine.java
index ed4f313..14da019 100644
--- a/src/com/android/contacts/editor/AggregationSuggestionEngine.java
+++ b/src/com/android/contacts/editor/AggregationSuggestionEngine.java
@@ -103,8 +103,6 @@
 
     private static final long SUGGESTION_LOOKUP_DELAY_MILLIS = 300;
 
-    private static final int MAX_SUGGESTION_COUNT = 3;
-
     private final Context mContext;
 
     private long[] mSuggestedContactIds = new long[0];
@@ -116,6 +114,7 @@
     private Cursor mDataCursor;
     private ContentObserver mContentObserver;
     private Uri mSuggestionsUri;
+    private int mSuggestionsLimit = 3;
 
     public AggregationSuggestionEngine(Context context) {
         super("AggregationSuggestions", Process.THREAD_PRIORITY_BACKGROUND);
@@ -147,6 +146,10 @@
         }
     }
 
+    public void setSuggestionsLimit(int suggestionsLimit) {
+        mSuggestionsLimit = suggestionsLimit;
+    }
+
     public void setListener(Listener listener) {
         mListener = listener;
     }
@@ -219,7 +222,7 @@
         }
 
         Builder builder = new AggregationSuggestions.Builder()
-                .setLimit(MAX_SUGGESTION_COUNT)
+                .setLimit(mSuggestionsLimit)
                 .setContactId(mContactId);
 
         if (nameSb.length() != 0) {
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
index 091f41a..3ac9472 100644
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java
@@ -45,6 +45,7 @@
 import android.os.Bundle;
 import android.os.Trace;
 import android.provider.CalendarContract;
+import android.provider.ContactsContract;
 import android.provider.ContactsContract.CommonDataKinds.Email;
 import android.provider.ContactsContract.CommonDataKinds.Event;
 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
@@ -67,6 +68,7 @@
 import android.provider.ContactsContract.QuickContact;
 import android.provider.ContactsContract.RawContacts;
 import android.support.v7.graphics.Palette;
+import android.support.v7.widget.CardView;
 import android.telecom.PhoneAccount;
 import android.telecom.TelecomManager;
 import android.text.BidiFormatter;
@@ -76,6 +78,7 @@
 import android.util.Log;
 import android.view.ContextMenu;
 import android.view.ContextMenu.ContextMenuInfo;
+import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
@@ -84,6 +87,11 @@
 import android.view.View.OnClickListener;
 import android.view.View.OnCreateContextMenuListener;
 import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
 import android.widget.Toast;
 import android.widget.Toolbar;
 
@@ -94,6 +102,7 @@
 import com.android.contacts.common.CallUtil;
 import com.android.contacts.common.ClipboardUtils;
 import com.android.contacts.common.Collapser;
+import com.android.contacts.common.ContactPhotoManager;
 import com.android.contacts.common.ContactsUtils;
 import com.android.contacts.common.activity.RequestPermissionsActivity;
 import com.android.contacts.common.dialog.CallSubjectDialog;
@@ -122,6 +131,7 @@
 import com.android.contacts.common.model.dataitem.StructuredNameDataItem;
 import com.android.contacts.common.model.dataitem.StructuredPostalDataItem;
 import com.android.contacts.common.model.dataitem.WebsiteDataItem;
+import com.android.contacts.common.model.ValuesDelta;
 import com.android.contacts.common.util.ImplicitIntentsUtil;
 import com.android.contacts.common.util.DateUtils;
 import com.android.contacts.common.util.MaterialColorMapUtils;
@@ -129,12 +139,15 @@
 import com.android.contacts.common.util.UriUtils;
 import com.android.contacts.common.util.ViewUtil;
 import com.android.contacts.detail.ContactDisplayUtils;
+import com.android.contacts.editor.AggregationSuggestionEngine;
+import com.android.contacts.editor.AggregationSuggestionEngine.Suggestion;
 import com.android.contacts.editor.ContactEditorFragment;
 import com.android.contacts.editor.EditorIntents;
 import com.android.contacts.interactions.CalendarInteractionsLoader;
 import com.android.contacts.interactions.CallLogInteractionsLoader;
 import com.android.contacts.interactions.ContactDeletionInteraction;
 import com.android.contacts.interactions.ContactInteraction;
+import com.android.contacts.interactions.JoinContactsDialogFragment;
 import com.android.contacts.interactions.SmsInteractionsLoader;
 import com.android.contacts.quickcontact.ExpandingEntryCardView.Entry;
 import com.android.contacts.quickcontact.ExpandingEntryCardView.EntryContextMenuInfo;
@@ -160,8 +173,11 @@
 import java.util.Comparator;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
 
 /**
@@ -169,7 +185,8 @@
  * data asynchronously, and then shows a popup with details centered around
  * {@link Intent#getSourceBounds()}.
  */
-public class QuickContactActivity extends ContactsActivity {
+public class QuickContactActivity extends ContactsActivity
+        implements AggregationSuggestionEngine.Listener {
 
     /**
      * QuickContacts immediately takes up the full screen. All possible information is shown.
@@ -181,6 +198,8 @@
     private static final String TAG = "QuickContact";
 
     private static final String KEY_THEME_COLOR = "theme_color";
+    private static final String KEY_IS_SUGGESTION_LIST_COLLAPSED = "is_suggestion_list_collapsed";
+    private static final String KEY_SELECTED_SUGGESTION_CONTACTS = "selected_suggestion_contacts";
 
     private static final int ANIMATION_STATUS_BAR_COLOR_CHANGE_DURATION = 150;
     private static final int REQUEST_CODE_CONTACT_EDITOR_ACTIVITY = 1;
@@ -224,10 +243,28 @@
     private ExpandingEntryCardView mNoContactDetailsCard;
     private ExpandingEntryCardView mRecentCard;
     private ExpandingEntryCardView mAboutCard;
+
+    // Suggestion card.
+    private CardView mSuggestionCardView;
+    private ImageView mSuggestionSummaryPhoto;
+    private TextView mSuggestionForName;
+    private TextView mSuggestionNumber;
+    private TextView mSuggestionSummary;
+    private ImageView mSuggestionExpansionButton;
+    private LinearLayout mSuggestionList;
+    private View mSuggestionSeparator;
+    private Button mSuggestionsMergeButton;
+    private boolean mIsSuggestionListCollapsed;
+
     private MultiShrinkScroller mScroller;
     private SelectAccountDialogFragmentListener mSelectAccountFragmentListener;
     private AsyncTask<Void, Void, Cp2DataCardModel> mEntriesAndActionsTask;
     private AsyncTask<Void, Void, Void> mRecentDataTask;
+
+    private AggregationSuggestionEngine mAggregationSuggestionEngine;
+    private List<Suggestion> mSuggestions;
+
+    private TreeSet<Long> mSelectedAggregationIds = new TreeSet<>();
     /**
      * The last copy of Cp2DataCardModel that was passed to {@link #populateContactAndAboutCard}.
      */
@@ -442,6 +479,154 @@
         }
     };
 
+    @Override
+    public void onAggregationSuggestionChange() {
+        mSuggestions = mAggregationSuggestionEngine.getSuggestions();
+        mSuggestionCardView.setVisibility(View.GONE);
+        mSuggestionList.removeAllViews();
+
+        final String suggestionForName = mContactData.getDisplayName();
+        final int suggestionNumber = mSuggestions.size();
+        final String suggestionSummary = getSuggestionAccountSummary(mSuggestions);
+
+        if (suggestionNumber > 0) {
+            mSuggestionCardView.setVisibility(View.VISIBLE);
+
+            // Take the first suggestion 's photo as the summary photo.
+            // TODO: take all suggestions' photos.
+            final Suggestion firstSuggestion = mSuggestions.get(0);
+            if (firstSuggestion.photo != null) {
+                mSuggestionSummaryPhoto.setImageBitmap(BitmapFactory.decodeByteArray(
+                        firstSuggestion.photo, 0, firstSuggestion.photo.length));
+            } else {
+                mSuggestionSummaryPhoto.setImageDrawable(
+                        ContactPhotoManager.getDefaultAvatarDrawableForContact(
+                                getResources(), false, null));
+            }
+
+            mSuggestionForName.setText(suggestionForName);
+            mSuggestionNumber.setText(getResources().getQuantityString(
+                    R.plurals.quickcontact_suggestions_number, suggestionNumber, suggestionNumber));
+            mSuggestionSummary.setText(suggestionSummary);
+
+            for (Suggestion suggestion : mSuggestions) {
+                mSuggestionList.addView(inflateSuggestionListView(suggestion));
+            }
+
+            mSuggestionExpansionButton.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View view) {
+                    if (mIsSuggestionListCollapsed) {
+                        expandSuggestionList();
+                    } else {
+                        collapseSuggestionList();
+                    }
+                }
+            });
+
+        } else {
+            mSuggestionCardView.setVisibility(View.GONE);
+        }
+    }
+
+    private void collapseSuggestionList() {
+        mSuggestionList.setVisibility(View.GONE);
+        mSuggestionSeparator.setVisibility(View.GONE);
+        mSuggestionExpansionButton.setImageResource(
+                R.drawable.ic_menu_expander_minimized_holo_light);
+        mIsSuggestionListCollapsed = true;
+    }
+
+    private void expandSuggestionList() {
+        mSuggestionList.setVisibility(View.VISIBLE);
+        mSuggestionSeparator.setVisibility(View.VISIBLE);
+        mSuggestionExpansionButton.setImageResource(
+                R.drawable.ic_menu_expander_maximized_holo_light);
+        mIsSuggestionListCollapsed = false;
+    }
+    /**
+     * Return summary like "Google(2),LinkedIn" for 3 suggestions.
+     */
+    private String getSuggestionAccountSummary(List<Suggestion> suggestions) {
+        Map<String, Integer> accountTypeMap = new HashMap<String, Integer>();
+        for (Suggestion suggestion : suggestions) {
+            final com.android.contacts.editor.AggregationSuggestionEngine.RawContact rawContact =
+                    suggestion.rawContacts.get(0);
+            final String displayAccountType = getDisplayAccountType(
+                    rawContact.accountType, rawContact.dataSet);
+            if (accountTypeMap.containsKey(displayAccountType)) {
+                int count = accountTypeMap.get(displayAccountType);
+                count++;
+                accountTypeMap.put(displayAccountType, count);
+            } else {
+                accountTypeMap.put(displayAccountType, 1);
+            }
+        }
+
+        Set<String> accountTypeWithNumber = new HashSet<>();
+        for (String accountType : accountTypeMap.keySet()) {
+            final String number = getResources().getQuantityString(
+                    R.plurals.quickcontact_suggestion_account_type_number,
+                    accountTypeMap.get(accountType),
+                    accountTypeMap.get(accountType));
+            accountTypeWithNumber.add(getResources().getString(
+                    R.string.quickcontact_suggestion_account_type, accountType, number));
+        }
+        return TextUtils.join(",", accountTypeWithNumber);
+    }
+
+    private String getDisplayAccountType(String accountTypeString, String dataSet) {
+        final AccountTypeManager accountTypeManager = AccountTypeManager.getInstance(this);
+        final AccountType accountType = accountTypeManager.getAccountType(
+                accountTypeString, dataSet);
+        return accountType.getDisplayLabel(this).toString();
+    }
+
+    private View inflateSuggestionListView(Suggestion suggestion) {
+        final LayoutInflater layoutInflater = LayoutInflater.from(this);
+        final View suggestionView = layoutInflater.inflate(
+                R.layout.quickcontact_suggestion_contact_item, null);
+
+        final ImageView photo = (ImageView) suggestionView.findViewById(
+                R.id.aggregation_suggestion_photo);
+        if (suggestion.photo != null) {
+            photo.setImageBitmap(BitmapFactory.decodeByteArray(
+                    suggestion.photo, 0, suggestion.photo.length));
+        } else {
+            photo.setImageDrawable(ContactPhotoManager.getDefaultAvatarDrawableForContact(
+                    getResources(), false, null));
+        }
+
+        final TextView name = (TextView) suggestionView.findViewById(R.id.aggregation_suggestion_name);
+        name.setText(suggestion.name);
+
+        final TextView accountTypeView = (TextView) suggestionView.findViewById(
+                R.id.aggregation_suggestion_account_type);
+        final String accountTypeString = suggestion.rawContacts.get(0).accountType;
+        final String dataSet = suggestion.rawContacts.get(0).dataSet;
+        final String displayAccountType = getDisplayAccountType(accountTypeString, dataSet);
+        if (!TextUtils.isEmpty(displayAccountType)) {
+            accountTypeView.setText(displayAccountType);
+        }
+
+        final CheckBox checkbox = (CheckBox) suggestionView.findViewById(R.id.suggestion_checkbox);
+        checkbox.setChecked(mSelectedAggregationIds.contains(suggestion.contactId));
+        checkbox.setTag(suggestion.contactId);
+        checkbox.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                final CheckBox checkBox = (CheckBox) v;
+                final Long contactId = (Long) checkBox.getTag();
+                if (checkBox.isChecked()) {
+                    mSelectedAggregationIds.add(contactId);
+                } else {
+                    mSelectedAggregationIds.remove(contactId);
+                }
+            }
+        });
+        return suggestionView;
+    }
+
     private interface ContextMenuIds {
         static final int COPY_TEXT = 0;
         static final int CLEAR_DEFAULT = 1;
@@ -715,6 +900,39 @@
         mRecentCard = (ExpandingEntryCardView) findViewById(R.id.recent_card);
         mAboutCard = (ExpandingEntryCardView) findViewById(R.id.about_card);
 
+        mSuggestionCardView = (CardView) findViewById(R.id.suggestion_card_view);
+        mSuggestionSummaryPhoto = (ImageView) findViewById(R.id.suggestion_icon);
+        mSuggestionForName = (TextView) findViewById(R.id.suggestion_for_name);
+        mSuggestionNumber = (TextView) findViewById(R.id.suggestion_number);
+        mSuggestionSummary = (TextView) findViewById(R.id.suggestion_summary);
+        mSuggestionExpansionButton = (ImageView) findViewById(R.id.expand_suggestion_button);
+        mSuggestionSeparator = findViewById(R.id.title_separator2);
+        mSuggestionList = (LinearLayout) findViewById(R.id.suggestion_list);
+        mSuggestionsMergeButton = (Button) findViewById(R.id.merge_button);
+        if (savedInstanceState != null) {
+            mIsSuggestionListCollapsed = savedInstanceState.getBoolean(
+                    KEY_IS_SUGGESTION_LIST_COLLAPSED, true);
+            mSelectedAggregationIds = (TreeSet<Long>)
+                    savedInstanceState.getSerializable(KEY_SELECTED_SUGGESTION_CONTACTS);
+        } else {
+            mIsSuggestionListCollapsed = true;
+            mSelectedAggregationIds.clear();
+        }
+
+        mSuggestionExpansionButton.setClickable(true);
+        mSuggestionsMergeButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                // Join selected contacts.
+                if (!mSelectedAggregationIds.contains(mContactData.getId())) {
+                    mSelectedAggregationIds.add(mContactData.getId());
+                }
+                TreeSet<Long> mergedContactIds = new TreeSet<Long>(mSelectedAggregationIds);
+                mSelectedAggregationIds.clear(); // Clear selected ids for merged contact.
+                JoinContactsDialogFragment.start(QuickContactActivity.this, mergedContactIds);
+            }
+        });
+
         mNoContactDetailsCard.setOnClickListener(mEntryClickHandler);
         mContactCard.setOnClickListener(mEntryClickHandler);
         mContactCard.setExpandButtonText(
@@ -844,6 +1062,9 @@
         if (mColorFilter != null) {
             savedInstanceState.putInt(KEY_THEME_COLOR, mColorFilterColor);
         }
+        savedInstanceState.putBoolean(KEY_IS_SUGGESTION_LIST_COLLAPSED, mIsSuggestionListCollapsed);
+        savedInstanceState.putSerializable(
+                KEY_SELECTED_SUGGESTION_CONTACTS, mSelectedAggregationIds);
     }
 
     private void processIntent(Intent intent) {
@@ -982,6 +1203,7 @@
     private void bindDataToCards(Cp2DataCardModel cp2DataCardModel) {
         startInteractionLoaders(cp2DataCardModel);
         populateContactAndAboutCard(cp2DataCardModel);
+        populateSuggestionCard();
     }
 
     private void startInteractionLoaders(Cp2DataCardModel cp2DataCardModel) {
@@ -1085,6 +1307,34 @@
         }
     }
 
+    private void populateSuggestionCard() {
+        // Initialize suggestion related view and data.
+        if (mIsSuggestionListCollapsed) {
+            collapseSuggestionList();
+        } else {
+            expandSuggestionList();
+        }
+        mSuggestionCardView.setVisibility(View.GONE);
+        mSuggestionList.removeAllViews();
+
+        if (mAggregationSuggestionEngine == null) {
+            mAggregationSuggestionEngine = new AggregationSuggestionEngine(this);
+            mAggregationSuggestionEngine.setListener(this);
+            mAggregationSuggestionEngine.setSuggestionsLimit(10);
+            mAggregationSuggestionEngine.start();
+        }
+
+        mAggregationSuggestionEngine.setContactId(mContactData.getId());
+
+        // Trigger suggestion engine to compute suggestions.
+        final ContentValues values = new ContentValues();
+        values.put(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME,
+                mContactData.getDisplayName());
+        values.put(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_FAMILY_NAME,
+                mContactData.getPhoneticName());
+        mAggregationSuggestionEngine.onNameChange(ValuesDelta.fromBefore(values));
+    }
+
     private void populateContactAndAboutCard(Cp2DataCardModel cp2DataCardModel) {
         mCachedCp2DataCardModel = cp2DataCardModel;
         if (mHasIntentLaunched || cp2DataCardModel == null) {
@@ -2248,6 +2498,14 @@
         }
     }
 
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (mAggregationSuggestionEngine != null) {
+            mAggregationSuggestionEngine.quit();
+        }
+    }
+
     /**
      * Returns true if it is possible to edit the current contact.
      */