Show popup for aggregation suggestion

- create ListPopupWindow instead of embedding Views into
  the base layout.
- refrain AggregationSuggestionView from having focusable and
  clickable capability, which will be automatically supplied from
  ListView
- tweak layout xmls to show ListPopupWindow correctly. Include
  some pixel-perfect designs

Bug: 5057400
Change-Id: I9ec8642c0864204747ee445da7ee0292171a8788
diff --git a/res/layout-sw580dp/aggregation_suggestions_item.xml b/res/layout-sw580dp/aggregation_suggestions_item.xml
deleted file mode 100644
index 5ea8347..0000000
--- a/res/layout-sw580dp/aggregation_suggestions_item.xml
+++ /dev/null
@@ -1,61 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
- * Copyright 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.
- */
--->
-
-<view xmlns:android="http://schemas.android.com/apk/res/android"
-    class="com.android.contacts.editor.AggregationSuggestionView"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="horizontal"
-    android:paddingLeft="5dip"
-    android:paddingRight="15dip"
-    android:background="?android:attr/selectableItemBackground"
-    android:focusable="true"
->
-    <ImageView
-        android:id="@+id/aggregation_suggestion_photo"
-        android:layout_width="@dimen/aggregation_suggestion_icon_size"
-        android:layout_height="@dimen/aggregation_suggestion_icon_size"
-        android:layout_alignParentLeft="true"
-        android:layout_centerInParent="true"
-        android:layout_marginTop="4dip"
-        android:scaleType="fitCenter"
-    />
-
-    <TextView
-        android:id="@+id/aggregation_suggestion_name"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_toRightOf="@id/aggregation_suggestion_photo"
-        android:layout_marginLeft="10dip"
-        android:layout_marginTop="4dip"
-        android:textAppearance="?android:attr/textAppearanceLarge"
-        android:textColor="?android:attr/textColorSecondary"
-    />
-
-    <TextView
-        android:id="@+id/aggregation_suggestion_data"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_toRightOf="@id/aggregation_suggestion_photo"
-        android:layout_below="@id/aggregation_suggestion_name"
-        android:layout_marginLeft="10dip"
-        android:textAppearance="?android:attr/textAppearanceMedium"
-        android:textColor="?android:attr/textColorSecondary"
-    />
-</view>
diff --git a/res/layout/account_selector_list_item.xml b/res/layout/account_selector_list_item.xml
index 82b73da..a700866 100644
--- a/res/layout/account_selector_list_item.xml
+++ b/res/layout/account_selector_list_item.xml
@@ -22,7 +22,7 @@
         android:layout_width="@dimen/detail_network_icon_size"
         android:layout_height="@dimen/detail_network_icon_size"
         android:layout_margin="8dip"
-        android:layout_gravity="center_vertical"/>
+        android:layout_gravity="center_vertical" />
 
     <LinearLayout
         android:layout_width="0dip"
diff --git a/res/layout/aggregation_suggestions_item.xml b/res/layout/aggregation_suggestions_item.xml
index 9ed1bf3..188a26e 100644
--- a/res/layout/aggregation_suggestions_item.xml
+++ b/res/layout/aggregation_suggestions_item.xml
@@ -21,42 +21,34 @@
     class="com.android.contacts.editor.AggregationSuggestionView"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:orientation="horizontal"
-    android:paddingLeft="5dip"
-    android:paddingRight="15dip"
+    android:minHeight="48dip"
+    android:paddingLeft="8dip"
     android:background="?android:attr/selectableItemBackground"
-    android:focusable="true"
->
+    android:orientation="horizontal">
+
+    <LinearLayout
+        android:layout_width="0px"
+        android:layout_height="wrap_content"
+        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:textAppearance="?android:attr/textAppearanceMedium" />
+        <TextView
+            android:id="@+id/aggregation_suggestion_data"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:textColor="?android:attr/textColorSecondary" />
+    </LinearLayout>
 
     <ImageView
         android:id="@+id/aggregation_suggestion_photo"
-        android:layout_width="@dimen/aggregation_suggestion_icon_size"
-        android:layout_height="@dimen/aggregation_suggestion_icon_size"
-        android:layout_alignParentLeft="true"
-        android:layout_centerInParent="true"
-        android:layout_marginTop="4dip"
+        android:layout_width="48dip"
+        android:layout_height="48dip"
         android:scaleType="fitCenter"
-    />
-
-    <TextView
-        android:id="@+id/aggregation_suggestion_name"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_toRightOf="@id/aggregation_suggestion_photo"
-        android:layout_marginLeft="10dip"
-        android:layout_marginTop="4dip"
-        android:textAppearance="?android:attr/textAppearanceSmall"
-        android:textColor="?android:attr/textColorSecondary"
-    />
-
-    <TextView
-        android:id="@+id/aggregation_suggestion_data"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_toRightOf="@id/aggregation_suggestion_photo"
-        android:layout_below="@id/aggregation_suggestion_name"
-        android:layout_marginLeft="10dip"
-        android:textAppearance="?android:attr/textAppearanceSmall"
-        android:textColor="?android:attr/textColorSecondary"
-    />
+        android:layout_gravity="center_vertical" />
 </view>
diff --git a/res/layout/edit_field_list.xml b/res/layout/edit_field_list.xml
index ba715c7..d46828b 100644
--- a/res/layout/edit_field_list.xml
+++ b/res/layout/edit_field_list.xml
@@ -23,4 +23,4 @@
     android:layout_weight="1"
     android:layout_height="wrap_content"
     android:paddingLeft="@dimen/editor_field_left_padding"
-    android:orientation="vertical" />
\ No newline at end of file
+    android:orientation="vertical" />
diff --git a/res/layout/edit_field_list_with_anchor_view.xml b/res/layout/edit_field_list_with_anchor_view.xml
new file mode 100644
index 0000000..fa69a5c
--- /dev/null
+++ b/res/layout/edit_field_list_with_anchor_view.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Layout that behaves similarly to edit_field_list.xml,
+     but also has an anchor view for ListPopupWindow -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="0dip"
+    android:layout_height="wrap_content"
+    android:layout_weight="1"
+    android:paddingLeft="@dimen/editor_field_left_padding"
+    android:orientation="vertical">
+    <LinearLayout
+         android:id="@+id/editors"
+         android:layout_width="match_parent"
+         android:layout_height="wrap_content"
+         android:orientation="vertical" />
+    <View
+         android:id="@+id/anchor_view"
+         android:layout_width="match_parent"
+         android:layout_height="0px" />
+</LinearLayout>
diff --git a/res/layout/raw_contact_editor_view.xml b/res/layout/raw_contact_editor_view.xml
index b98f4fb..af95e04 100644
--- a/res/layout/raw_contact_editor_view.xml
+++ b/res/layout/raw_contact_editor_view.xml
@@ -66,13 +66,6 @@
 
         </LinearLayout>
 
-        <ViewStub android:id="@+id/aggregation_suggestion_stub"
-            android:inflatedId="@+id/aggregation_suggestion"
-            android:layout="@layout/aggregation_suggestions"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:visibility="visible"/>
-
         <LinearLayout
             android:id="@+id/sect_fields"
             android:layout_width="match_parent"
diff --git a/res/layout/structured_name_editor_view.xml b/res/layout/structured_name_editor_view.xml
index 196b079..3c5d5c9 100644
--- a/res/layout/structured_name_editor_view.xml
+++ b/res/layout/structured_name_editor_view.xml
@@ -34,8 +34,7 @@
         android:clickable="true">
 
         <include
-            android:id="@+id/editors"
-            layout="@layout/edit_field_list" />
+            layout="@layout/edit_field_list_with_anchor_view" />
 
         <include
             android:id="@+id/expansion_view_container"
diff --git a/res/layout/text_fields_editor_view.xml b/res/layout/text_fields_editor_view.xml
index e63b7da..e187a9e 100644
--- a/res/layout/text_fields_editor_view.xml
+++ b/res/layout/text_fields_editor_view.xml
@@ -31,8 +31,7 @@
         android:clickable="true">
 
         <include
-            android:id="@+id/editors"
-            layout="@layout/edit_field_list" />
+            layout="@layout/edit_field_list_with_anchor_view" />
 
         <include
             android:id="@+id/expansion_view_container"
diff --git a/res/values-sw580dp/dimens.xml b/res/values-sw580dp/dimens.xml
index f7c0f05..91842d6 100644
--- a/res/values-sw580dp/dimens.xml
+++ b/res/values-sw580dp/dimens.xml
@@ -14,7 +14,6 @@
      limitations under the License.
 -->
 <resources>
-    <dimen name="aggregation_suggestion_icon_size">64dip</dimen>
     <dimen name="editor_padding_top">32dip</dimen>
     <dimen name="editor_type_label_width">122dip</dimen>
     <dimen name="editor_field_spinner_text_size">15sp</dimen>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index d5f3b69..1edc590 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -21,8 +21,6 @@
     <dimen name="contact_shortcut_frame_width">50dip</dimen>
     <dimen name="contact_shortcut_frame_height">56dip</dimen>
 
-    <dimen name="aggregation_suggestion_icon_size">40dip</dimen>
-
     <dimen name="account_selector_popup_width">400dip</dimen>
 
     <dimen name="photo_action_popup_width">400dip</dimen>
@@ -249,4 +247,5 @@
 
     <!-- Height for directory headers in contact lists -->
     <dimen name="directory_header_height">24dip</dimen>
+
 </resources>
diff --git a/src/com/android/contacts/editor/AggregationSuggestionView.java b/src/com/android/contacts/editor/AggregationSuggestionView.java
index 07e67e8..df90cff 100644
--- a/src/com/android/contacts/editor/AggregationSuggestionView.java
+++ b/src/com/android/contacts/editor/AggregationSuggestionView.java
@@ -29,7 +29,7 @@
 import android.provider.ContactsContract.Contacts;
 import android.util.AttributeSet;
 import android.widget.ImageView;
-import android.widget.RelativeLayout;
+import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import java.util.ArrayList;
@@ -38,7 +38,7 @@
 /**
  * A view that contains a name, picture and other data for a contact aggregation suggestion.
  */
-public class AggregationSuggestionView extends RelativeLayout {
+public class AggregationSuggestionView extends LinearLayout {
 
     public interface Listener {
 
@@ -63,17 +63,14 @@
 
     public AggregationSuggestionView(Context context) {
         super(context);
-        setClickable(true);
     }
 
     public AggregationSuggestionView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        setClickable(true);
     }
 
     public AggregationSuggestionView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
-        setClickable(true);
     }
 
     public void setNewContact(boolean flag) {
@@ -135,8 +132,7 @@
         mListener = listener;
     }
 
-    @Override
-    public boolean performClick() {
+    public boolean handleItemClickEvent() {
         if (mListener != null && isEnabled()) {
             if (canEditSuggestedContact()) {
                 mListener.onEditAction(Contacts.getLookupUri(mContactId, mLookupKey));
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index 412efab..afc3ab1 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -83,6 +83,8 @@
 import android.view.ViewGroup.MarginLayoutParams;
 import android.view.ViewStub;
 import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.BaseAdapter;
 import android.widget.LinearLayout;
 import android.widget.ListPopupWindow;
 import android.widget.Toast;
@@ -207,17 +209,6 @@
 
     private Cursor mGroupMetaData;
 
-    /**
-     * A delay in milliseconds used for bringing aggregation suggestions to
-     * the visible part of the screen. The reason this has to be done after
-     * a delay is a race condition with the soft keyboard.  The keyboard
-     * may expand to display its own autocomplete suggestions, which will
-     * reduce the visible area of the screen.  We will yield to the keyboard
-     * hoping that the delay is sufficient.  If not - part of the
-     * suggestion will be hidden, which is not fatal.
-     */
-    private static final int AGGREGATION_SUGGESTION_SCROLL_DELAY = 200;
-
     private File mCurrentPhotoFile;
 
     // Height/width (in pixels) to request for the photo - queried from the provider.
@@ -245,6 +236,62 @@
     private long mAggregationSuggestionsRawContactId;
     private View mAggregationSuggestionView;
 
+    private ListPopupWindow mAggregationSuggestionPopup;
+
+    private static final class AggregationSuggestionAdapter extends BaseAdapter {
+        private final Activity mActivity;
+        private final boolean mSetNewContact;
+        private final AggregationSuggestionView.Listener mListener;
+        private final List<Suggestion> mSuggestions;
+
+        public AggregationSuggestionAdapter(Activity activity, boolean setNewContact,
+                AggregationSuggestionView.Listener listener, List<Suggestion> suggestions) {
+            mActivity = activity;
+            mSetNewContact = setNewContact;
+            mListener = listener;
+            mSuggestions = suggestions;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            Suggestion suggestion = (Suggestion) getItem(position);
+            LayoutInflater inflater = mActivity.getLayoutInflater();
+            AggregationSuggestionView suggestionView =
+                    (AggregationSuggestionView) inflater.inflate(
+                            R.layout.aggregation_suggestions_item, null);
+            suggestionView.setNewContact(mSetNewContact);
+            suggestionView.setListener(mListener);
+            suggestionView.bindSuggestion(suggestion);
+            return suggestionView;
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+
+        @Override
+        public Object getItem(int position) {
+            return mSuggestions.get(position);
+        }
+
+        @Override
+        public int getCount() {
+            return mSuggestions.size();
+        }
+    }
+
+    private OnItemClickListener mAggregationSuggestionItemClickListener =
+            new OnItemClickListener() {
+        @Override
+        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+            final AggregationSuggestionView suggestionView = (AggregationSuggestionView) view;
+            suggestionView.handleItemClickEvent();
+            mAggregationSuggestionPopup.dismiss();
+            mAggregationSuggestionPopup = null;
+        }
+    };
+
     private boolean mAutoAddToDefaultGroup;
 
     private boolean mEnabled = true;
@@ -1276,76 +1323,28 @@
             return;
         }
 
-        RawContactEditorView rawContactView =
+        if (mAggregationSuggestionPopup != null && mAggregationSuggestionPopup.isShowing()) {
+            mAggregationSuggestionPopup.dismiss();
+        }
+
+        if (mAggregationSuggestionEngine.getSuggestedContactCount() == 0) {
+            return;
+        }
+
+        final RawContactEditorView rawContactView =
                 (RawContactEditorView)getRawContactEditorView(mAggregationSuggestionsRawContactId);
-        if (rawContactView == null) {
-            return;
-        }
-
-        ViewStub stub = (ViewStub)rawContactView.findViewById(R.id.aggregation_suggestion_stub);
-        if (stub != null) {
-            stub.inflate();
-        }
-
-        // Only request the view on screen when it is first displayed
-        boolean requestOnScreen = mAggregationSuggestionView == null;
-        mAggregationSuggestionView = rawContactView.findViewById(R.id.aggregation_suggestion);
-
-        int count = mAggregationSuggestionEngine.getSuggestedContactCount();
-        if (count == 0) {
-            mAggregationSuggestionView.setVisibility(View.GONE);
-            return;
-        }
-
-        List<Suggestion> suggestions = mAggregationSuggestionEngine.getSuggestions();
-
-        LinearLayout itemList = (LinearLayout) mAggregationSuggestionView.findViewById(
-                R.id.aggregation_suggestions);
-        itemList.removeAllViews();
-
-        LayoutInflater inflater = getActivity().getLayoutInflater();
-
-        for (Suggestion suggestion : suggestions) {
-            AggregationSuggestionView suggestionView =
-                    (AggregationSuggestionView) inflater.inflate(
-                            R.layout.aggregation_suggestions_item, null);
-            suggestionView.setLayoutParams(
-                    new LinearLayout.LayoutParams(
-                            LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
-            suggestionView.setNewContact(mState.size() == 1 && mState.get(0).isContactInsert());
-            suggestionView.setListener(this);
-            suggestionView.bindSuggestion(suggestion);
-            itemList.addView(suggestionView);
-        }
-
-        adjustAggregationSuggestionViewLayout(rawContactView);
-        setAggregationSuggestionViewEnabled(mEnabled);
-        mAggregationSuggestionView.setVisibility(View.VISIBLE);
-
-        if (requestOnScreen) {
-            mContent.postDelayed(new Runnable() {
-
-                @Override
-                public void run() {
-                    requestAggregationSuggestionOnScreen(mAggregationSuggestionView);
-                }
-            }, AGGREGATION_SUGGESTION_SCROLL_DELAY);
-        }
-    }
-
-    /**
-     * Adjusts the layout of the aggregation suggestion view so that it is placed directly
-     * underneath and have the same width as the last text editor of the contact name editor.
-     */
-    private void adjustAggregationSuggestionViewLayout(RawContactEditorView rawContactView) {
-        TextFieldsEditorView nameEditor = rawContactView.getNameEditor();
-        Rect rect = new Rect();
-        nameEditor.acquireEditorBounds(rect);
-        MarginLayoutParams layoutParams =
-                (MarginLayoutParams) mAggregationSuggestionView.getLayoutParams();
-        layoutParams.leftMargin = rect.left;
-        layoutParams.width = rect.width();
-        mAggregationSuggestionView.setLayoutParams(layoutParams);
+        final View anchorView = rawContactView.findViewById(R.id.anchor_view);
+        mAggregationSuggestionPopup = new ListPopupWindow(mContext, null);
+        mAggregationSuggestionPopup.setAnchorView(anchorView);
+        mAggregationSuggestionPopup.setWidth(anchorView.getWidth());
+        mAggregationSuggestionPopup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
+        mAggregationSuggestionPopup.setModal(true);
+        mAggregationSuggestionPopup.setAdapter(
+                new AggregationSuggestionAdapter(getActivity(),
+                        mState.size() == 1 && mState.get(0).isContactInsert(),
+                        this, mAggregationSuggestionEngine.getSuggestions()));
+        mAggregationSuggestionPopup.setOnItemClickListener(mAggregationSuggestionItemClickListener);
+        mAggregationSuggestionPopup.show();
     }
 
     @Override
@@ -1452,20 +1451,6 @@
         }
     }
 
-    /**
-     * Scrolls the editor if necessary to reveal the aggregation suggestion that is
-     * shown below the name editor. Makes sure that the currently focused field
-     * remains visible.
-     */
-    private void requestAggregationSuggestionOnScreen(final View view) {
-        Rect rect = getRelativeBounds(mContent, view);
-        View focused = mContent.findFocus();
-        if (focused != null) {
-            rect.union(getRelativeBounds(mContent, focused));
-        }
-        mContent.requestRectangleOnScreen(rect);
-    }
-
     public void setAggregationSuggestionViewEnabled(boolean enabled) {
         if (mAggregationSuggestionView == null) {
             return;