am 8213765a: am 2816207e: am d5b5d0a3: Add drag to remove for favorites in Dialer

* commit '8213765ac8e8767b7206a7008341b4a75e63dd3e':
  Add drag to remove for favorites in Dialer
diff --git a/res/drawable-hdpi/ic_remove.png b/res/drawable-hdpi/ic_remove.png
new file mode 100644
index 0000000..1ee6adf
--- /dev/null
+++ b/res/drawable-hdpi/ic_remove.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_remove_highlight.png b/res/drawable-hdpi/ic_remove_highlight.png
new file mode 100644
index 0000000..435ee36
--- /dev/null
+++ b/res/drawable-hdpi/ic_remove_highlight.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_remove.png b/res/drawable-mdpi/ic_remove.png
new file mode 100644
index 0000000..2c134ea
--- /dev/null
+++ b/res/drawable-mdpi/ic_remove.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_remove_highlight.png b/res/drawable-mdpi/ic_remove_highlight.png
new file mode 100644
index 0000000..6a961cb
--- /dev/null
+++ b/res/drawable-mdpi/ic_remove_highlight.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_remove.png b/res/drawable-xhdpi/ic_remove.png
new file mode 100644
index 0000000..be81592
--- /dev/null
+++ b/res/drawable-xhdpi/ic_remove.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_remove_highlight.png b/res/drawable-xhdpi/ic_remove_highlight.png
new file mode 100644
index 0000000..57949e3
--- /dev/null
+++ b/res/drawable-xhdpi/ic_remove_highlight.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_remove.png b/res/drawable-xxhdpi/ic_remove.png
new file mode 100644
index 0000000..2722f23
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_remove.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_remove_highlight.png b/res/drawable-xxhdpi/ic_remove_highlight.png
new file mode 100644
index 0000000..23ee8f6
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_remove_highlight.png
Binary files differ
diff --git a/res/layout/dialtacts_activity.xml b/res/layout/dialtacts_activity.xml
index e2c3853..5a886e2 100644
--- a/res/layout/dialtacts_activity.xml
+++ b/res/layout/dialtacts_activity.xml
@@ -28,20 +28,20 @@
         android:layout_height="match_parent"
         android:clipChildren="false"
         android:orientation="vertical" >
-        <LinearLayout
+        <FrameLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:id="@+id/search_view_container"
-            android:orientation="vertical"
             >
             <LinearLayout
                 android:layout_width="match_parent"
-                android:layout_height="match_parent"
+                android:layout_height="wrap_content"
+                android:id="@+id/search_view_container"
                 android:orientation="horizontal"
                 android:paddingLeft="16dp"
                 android:paddingRight="23dp"
                 android:background="@color/searchbox_background_color"
-                android:gravity="center_vertical">
+                android:gravity="center_vertical"
+                >
                 <EditText
                     android:id="@+id/search_view"
                     android:layout_width="0dp"
@@ -57,6 +57,7 @@
                     android:src="@drawable/ic_close_dk"
                     android:clickable="true"
                     android:background="?android:attr/selectableItemBackground"
+                    android:contentDescription="@string/description_clear_search"
                     android:visibility="gone" />
                 <ImageView
                     android:id="@+id/voice_search_button"
@@ -68,12 +69,35 @@
                     android:contentDescription="@string/description_start_voice_search"
                     android:background="?android:attr/selectableItemBackground" />
             </LinearLayout>
-            <View
-                android:id="@+id/searchbox_divider"
-                android:layout_height="1dp"
+            <com.android.dialer.list.RemoveView
                 android:layout_width="match_parent"
-                android:background="@color/background_dialer_light" />
-        </LinearLayout>
+                android:layout_height="56dp"
+                android:id="@+id/remove_view_container"
+                android:orientation="horizontal"
+                android:gravity="center"
+                android:visibility="gone">
+                <ImageView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:id="@+id/remove_view_icon"
+                    android:src="@drawable/ic_remove"
+                    android:contentDescription="@string/remove_contact"
+                />
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:id="@+id/remove_view_text"
+                    android:textSize="@dimen/remove_text_size"
+                    android:textColor="@color/remove_text_color"
+                    android:text="@string/remove_contact"
+                />
+            </com.android.dialer.list.RemoveView>
+        </FrameLayout>
+        <View
+            android:id="@+id/searchbox_divider"
+            android:layout_height="1dp"
+            android:layout_width="match_parent"
+            android:background="@color/background_dialer_light" />
         <FrameLayout
             android:layout_height="0dp"
             android:layout_weight="1"
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 370bdfe..fc8e847 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -12,7 +12,7 @@
   ~ 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
-  -->
+-->
 
 <resources>
 
@@ -27,8 +27,10 @@
     <!-- Color of the text describing an unconsumed voicemail. -->
     <color name="call_log_voicemail_highlight_color">#33b5e5</color>
 
-    <!-- Colour of voicemail progress bar to the right of position indicator.
-         Same as the background color of the dialer -->
+    <!--
+         Colour of voicemail progress bar to the right of position indicator.
+         Same as the background color of the dialer
+    -->
     <color name="voicemail_playback_seek_bar_yet_to_play">#cecece</color>
 
     <!-- Colour of voicemail progress bar to the left of position indicator. -->
@@ -38,7 +40,7 @@
     <color name="item_selected">#660099cc</color>
 
     <!-- Background color of new dialer activity -->
-    <color name="background_dialer_light">#cecece</color>
+    <color name="background_dialer_light">#eeeeee</color>
 
     <!-- Background color of dialer list items (contacts, call log entries) -->
     <color name="background_dialer_list_items">#eeeeee</color>
@@ -71,6 +73,12 @@
     <!-- Text color for no favorites message -->
     <color name="nofavorite_text_color">#777777</color>
 
+    <!-- Text color for the "Remove" text in its regular state -->
+    <color name="remove_text_color">#555555</color>
+
+    <!-- Text color for the "Remove" text when a contact is dragged on top of the remove view -->
+    <color name="remove_highlighted_text_color">#FF3F3B</color>
+
     <!-- Text color for the "speed dial" label in the favorites menu. -->
     <color name="speed_dial_text_color">#555555</color>
 
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 3c856d2..fb74e97 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -13,7 +13,7 @@
   ~ 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
-  -->
+-->
 <resources>
 
     <!-- Height of edit text in dialpad fragment -->
@@ -29,9 +29,15 @@
     <!-- Match call_button_height to Phone's dimens/in_call_end_button_height -->
     <dimen name="call_button_height">74dp</dimen>
 
-    <!--  Search View -->
+    <!-- Search View -->
     <dimen name="search_text_size">14sp</dimen>
 
+    <!--
+          Drag to remove view (in dp because it is used in conjunction with a statically
+          sized icon
+    -->
+    <dimen name="remove_text_size">16dp</dimen>
+
     <!-- Call Log -->
     <dimen name="call_log_call_action_size">32dip</dimen>
     <dimen name="call_log_call_action_width">48dip</dimen>
@@ -51,9 +57,11 @@
          the main area of a call log entry and the secondary action button. -->
     <dimen name="call_log_list_item_vertical_divider_width">1dp</dimen>
 
-    <!-- Layout weight values for dialpad screen. These layouts will be used in one
+    <!--
+         Layout weight values for dialpad screen. These layouts will be used in one
          LinearLayout (dialpad_fragment.xml), configuring dialpad screen's vertical
-         ratio. -->
+         ratio.
+    -->
     <integer name="dialpad_layout_weight_digits">15</integer>
     <integer name="dialpad_layout_weight_dialpad">65</integer>
 
@@ -63,14 +71,14 @@
     <dimen name="dialpad_key_plus_size">15dp</dimen>
     <dimen name="dialpad_key_special_characters_size">25dp</dimen>
     <dimen name="dialpad_key_letters_width">41dp</dimen>
-
-
     <dimen name="fake_action_bar_height">60dp</dimen>
     <!-- Min with of fake menu buttons, which should be same as ActionBar's one -->
     <dimen name="fake_menu_button_min_width">56dp</dimen>
 
     <!--  Favorites tile and recent call log padding -->
     <dimen name="contact_tile_divider_width">12dp</dimen>
+    <!-- Favorites tile and recent call log padding -->
+    <dimen name="contact_tile_divider_padding">3dp</dimen>
     <dimen name="contact_tile_info_button_height_and_width">36dp</dimen>
     <item name="contact_tile_height_to_width_ratio" type="dimen">67%</item>
     <dimen name="favorites_row_top_padding">6dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 2564cd7..20a73c1 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -533,6 +533,9 @@
     -->
     <string name="description_call_log_unheard_voicemail">Unheard voicemail</string>
 
+    <!--  String describing the icon used to clear the search field -->
+    <string name="description_clear_search">Clear search</string>
+
     <!-- String describing the icon used to start a voice search -->
     <string name="description_start_voice_search">Start voice search</string>
 
@@ -720,6 +723,8 @@
     <!-- Content description for dismiss button on badge. [CHAR LIMIT=NONE] -->
     <string name="description_dismiss">Dismiss</string>
 
+    <!-- Remove button that shows up when contact is long-pressed. [CHAR LIMIT=NONE] -->
+    <string name="remove_contact">Remove</string>
     <!-- Header text displayed on the main dialer screen above the list of favorite phone numbers.
          [CHAR LIMIT=21] -->
     <string name="favorites_menu_speed_dial">Speed Dial</string>
diff --git a/src/com/android/dialer/DialtactsActivity.java b/src/com/android/dialer/DialtactsActivity.java
index 0c0fcda..227cd8e 100644
--- a/src/com/android/dialer/DialtactsActivity.java
+++ b/src/com/android/dialer/DialtactsActivity.java
@@ -22,7 +22,6 @@
 import android.app.Activity;
 import android.app.Fragment;
 import android.app.FragmentManager;
-import android.app.FragmentManager.BackStackEntry;
 import android.app.FragmentTransaction;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
@@ -50,8 +49,6 @@
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnFocusChangeListener;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.AbsListView.OnScrollListener;
 import android.widget.EditText;
@@ -70,9 +67,13 @@
 import com.android.dialer.dialpad.SmartDialPrefix;
 import com.android.dialer.interactions.PhoneNumberInteraction;
 import com.android.dialer.list.AllContactsActivity;
+import com.android.dialer.list.DragDropController;
+import com.android.dialer.list.OnDragDropListener;
 import com.android.dialer.list.OnListFragmentScrolledListener;
 import com.android.dialer.list.PhoneFavoriteFragment;
+import com.android.dialer.list.PhoneFavoriteTileView;
 import com.android.dialer.list.RegularSearchFragment;
+import com.android.dialer.list.RemoveView;
 import com.android.dialer.list.SearchFragment;
 import com.android.dialer.list.SmartDialSearchFragment;
 import com.android.dialerbind.DatabaseHelperManager;
@@ -88,7 +89,9 @@
         DialpadFragment.OnDialpadQueryChangedListener, PopupMenu.OnMenuItemClickListener,
         OnListFragmentScrolledListener,
         DialpadFragment.OnDialpadFragmentStartedListener,
-        PhoneFavoriteFragment.OnShowAllContactsListener {
+        PhoneFavoriteFragment.OnShowAllContactsListener,
+        PhoneFavoriteFragment.HostInterface,
+        OnDragDropListener {
     private static final String TAG = "DialtactsActivity";
 
     public static final boolean DEBUG = false;
@@ -122,6 +125,8 @@
 
     private static final int ACTIVITY_REQUEST_CODE_VOICE_SEARCH = 1;
 
+    private static final int FADE_ANIMATION_DURATION = 200;
+
     private String mFilterText;
 
     /**
@@ -168,6 +173,7 @@
      */
     private boolean mFirstLaunch;
     private View mSearchViewContainer;
+    private RemoveView mRemoveViewContainer;
     private View mSearchViewCloseButton;
     private View mVoiceSearchButton;
     private EditText mSearchView;
@@ -312,6 +318,7 @@
         mBottomPaddingView = findViewById(R.id.dialtacts_bottom_padding);
         mFragmentsFrame = findViewById(R.id.dialtacts_frame);
         mActionBar = findViewById(R.id.fake_action_bar);
+        mRemoveViewContainer = (RemoveView) findViewById(R.id.remove_view_container);
         prepareSearchView();
 
         if (UI.FILTER_CONTACTS_ACTION.equals(intent.getAction())
@@ -977,4 +984,48 @@
                 PackageManager.MATCH_DEFAULT_ONLY);
         return resolveInfo != null && resolveInfo.size() > 0;
     }
+
+    @Override
+    public void onDragStarted(int itemIndex, int x, int y, PhoneFavoriteTileView view) {
+        crossfadeViews(mRemoveViewContainer, mSearchViewContainer, FADE_ANIMATION_DURATION);
+    }
+
+    @Override
+    public void onDragHovered(int itemIndex, int x, int y) {}
+
+    @Override
+    public void onDragFinished(int x, int y) {
+        crossfadeViews(mSearchViewContainer, mRemoveViewContainer, FADE_ANIMATION_DURATION);
+    }
+
+    @Override
+    public void onDroppedOnRemove() {}
+
+    /**
+     * Allows the PhoneFavoriteFragment to attach the drag controller to mRemoveViewContainer
+     * once it has been attached to the activity.
+     */
+    @Override
+    public void setDragDropController(DragDropController dragController) {
+        mRemoveViewContainer.setDragDropController(dragController);
+    }
+
+    /**
+     * Crossfades two views so that the first one appears while the other one is fading
+     * out of view.
+     */
+    private void crossfadeViews(final View fadeIn, final View fadeOut, int duration) {
+        fadeOut.animate().alpha(0).setDuration(duration)
+        .setListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                fadeOut.setVisibility(View.GONE);
+            }
+        });
+
+        fadeIn.setVisibility(View.VISIBLE);
+        fadeIn.setAlpha(0);
+        fadeIn.animate().alpha(1).setDuration(FADE_ANIMATION_DURATION)
+                .setListener(null);
+    }
 }
diff --git a/src/com/android/dialer/list/DragDropController.java b/src/com/android/dialer/list/DragDropController.java
new file mode 100644
index 0000000..399cd09
--- /dev/null
+++ b/src/com/android/dialer/list/DragDropController.java
@@ -0,0 +1,71 @@
+package com.android.dialer.list;
+
+import android.view.View;
+
+import com.android.dialer.list.PhoneFavoritesTileAdapter.ContactTileRow;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class that handles and combines drag events generated from multiple views, and then fires
+ * off events to any OnDragDropListeners that have registered for callbacks.
+ */
+public class DragDropController {
+    private List<OnDragDropListener> mOnDragDropListeners = new ArrayList<OnDragDropListener>();
+
+    /**
+     * @return True if the drag is started, false if the drag is cancelled for some reason.
+     */
+    boolean handleDragStarted(int x, int y, ContactTileRow tileRow) {
+        final PhoneFavoriteTileView tileView =
+                (PhoneFavoriteTileView) tileRow.getViewAtPosition(x, y);
+
+        final int itemIndex = tileRow.getItemIndex(x, y);
+        if (itemIndex != -1 && !mOnDragDropListeners.isEmpty()) {
+            for (int i = 0; i < mOnDragDropListeners.size(); i++) {
+                mOnDragDropListeners.get(i).onDragStarted(itemIndex, x, y, tileView);
+            }
+        }
+
+        return true;
+    }
+
+    public void handleDragHovered(int x, int y, View view) {
+        int itemIndex;
+        if (!(view instanceof ContactTileRow)) {
+            itemIndex = -1;
+        } else {
+            final ContactTileRow tile = (ContactTileRow) view;
+            itemIndex = tile.getItemIndex(x, y);
+        }
+        for (int i = 0; i < mOnDragDropListeners.size(); i++) {
+            mOnDragDropListeners.get(i).onDragHovered(itemIndex, x, y);
+        }
+    }
+
+    public void handleDragFinished(int x, int y, boolean isRemoveView) {
+        if (isRemoveView) {
+            for (int i = 0; i < mOnDragDropListeners.size(); i++) {
+                mOnDragDropListeners.get(i).onDroppedOnRemove();
+            }
+        }
+
+        for (int i = 0; i < mOnDragDropListeners.size(); i++) {
+            mOnDragDropListeners.get(i).onDragFinished(x, y);
+        }
+    }
+
+    public void addOnDragDropListener(OnDragDropListener listener) {
+        if (!mOnDragDropListeners.contains(listener)) {
+            mOnDragDropListeners.add(listener);
+        }
+    }
+
+    public void removeOnDragDropListener(OnDragDropListener listener) {
+        if (mOnDragDropListeners.contains(listener)) {
+            mOnDragDropListeners.remove(listener);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/com/android/dialer/list/OnDragDropListener.java b/src/com/android/dialer/list/OnDragDropListener.java
new file mode 100644
index 0000000..7f6d179
--- /dev/null
+++ b/src/com/android/dialer/list/OnDragDropListener.java
@@ -0,0 +1,41 @@
+package com.android.dialer.list;
+
+
+/**
+ * Classes that want to receive callbacks in response to drag events should implement this
+ * interface.
+ */
+public interface OnDragDropListener {
+    /**
+     * Called when a drag is started.
+     * @param itemIndex Index of the contact on which the drag was triggered
+     * @param x X-coordinate of the drag event
+     * @param y Y-coordinate of the drag event
+     * @param view The contact tile which the drag was started on
+     */
+    public void onDragStarted(int itemIndex, int x, int y, PhoneFavoriteTileView view);
+
+    /**
+     * Called when a drag is in progress and the user moves the dragged contact to a
+     * location.
+     * @param itemIndex Index of the contact in the ListView which is currently being displaced
+     * by the dragged contact
+     * @param x X-coordinate of the drag event
+     * @param y Y-coordinate of the drag event
+     */
+    public void onDragHovered(int itemIndex, int x, int y);
+
+    /**
+     * Called when a drag is completed (whether by dropping it somewhere or simply by dragging
+     * the contact off the screen)
+     * @param x X-coordinate of the drag event
+     * @param y Y-coordinate of the drag event
+     */
+    public void onDragFinished(int x, int y);
+
+    /**
+     * Called when a contact has been dropped on the remove view, indicating that the user
+     * wants to remove this contact.
+     */
+    public void onDroppedOnRemove();
+}
\ No newline at end of file
diff --git a/src/com/android/dialer/list/PhoneFavoriteFragment.java b/src/com/android/dialer/list/PhoneFavoriteFragment.java
index 860f9dc..79dbe8c 100644
--- a/src/com/android/dialer/list/PhoneFavoriteFragment.java
+++ b/src/com/android/dialer/list/PhoneFavoriteFragment.java
@@ -107,6 +107,10 @@
         public void onCallNumberDirectly(String phoneNumber);
     }
 
+    public interface HostInterface {
+        public void setDragDropController(DragDropController controller);
+    }
+
     private class MissedCallLogLoaderListener implements LoaderManager.LoaderCallbacks<Cursor> {
 
         @Override
@@ -279,7 +283,7 @@
         mListView.setVerticalScrollbarPosition(View.SCROLLBAR_POSITION_RIGHT);
         mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
         mListView.setOnItemSwipeListener(mContactTileAdapter);
-        mListView.setOnDragDropListener(mContactTileAdapter);
+        mListView.getDragDropController().addOnDragDropListener(mContactTileAdapter);
 
         final ImageView dragShadowOverlay =
                 (ImageView) mParentView.findViewById(R.id.contact_tile_drag_shadow_overlay);
@@ -347,6 +351,15 @@
                     + " must implement OnShowAllContactsListener");
         }
 
+        try {
+            OnDragDropListener listener = (OnDragDropListener) activity;
+            mListView.getDragDropController().addOnDragDropListener(listener);
+            ((HostInterface) activity).setDragDropController(mListView.getDragDropController());
+        } catch (ClassCastException e) {
+            throw new ClassCastException(activity.toString()
+                    + " must implement OnDragDropListener and HostInterface");
+        }
+
         // Use initLoader() instead of restartLoader() to refraining unnecessary reload.
         // This method call implicitly assures ContactTileLoaderListener's onLoadFinished() will
         // be called, on which we'll check if "all" contacts should be reloaded again or not.
diff --git a/src/com/android/dialer/list/PhoneFavoriteListView.java b/src/com/android/dialer/list/PhoneFavoriteListView.java
index 99979dd..adda3cf 100644
--- a/src/com/android/dialer/list/PhoneFavoriteListView.java
+++ b/src/com/android/dialer/list/PhoneFavoriteListView.java
@@ -29,7 +29,6 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
-import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.ListView;
 
@@ -44,7 +43,8 @@
  * - Swiping, which is borrowed from packages/apps/UnifiedEmail (com.android.mail.ui.Swipeable)
  * - Drag and drop
  */
-public class PhoneFavoriteListView extends ListView implements SwipeHelperCallback {
+public class PhoneFavoriteListView extends ListView implements SwipeHelperCallback,
+        OnDragDropListener {
 
     public static final String LOG_TAG = PhoneFavoriteListView.class.getSimpleName();
 
@@ -52,7 +52,6 @@
     private boolean mEnableSwipe = true;
 
     private OnItemGestureListener mOnItemGestureListener;
-    private OnDragDropListener mOnDragDropListener;
 
     private float mDensityScale;
     private float mTouchSlop;
@@ -81,6 +80,8 @@
     private int mDragShadowLeft;
     private int mDragShadowTop;
 
+    private DragDropController mDragDropController = new DragDropController();
+
     private final float DRAG_SHADOW_ALPHA = 0.7f;
 
     /**
@@ -130,6 +131,7 @@
         mSwipeHelper = new SwipeHelper(context, SwipeHelper.X, this,
                 mDensityScale, mTouchSlop);
         setItemsCanFocus(true);
+        mDragDropController.addOnDragDropListener(this);
     }
 
     @Override
@@ -156,10 +158,10 @@
         mOnItemGestureListener = listener;
     }
 
-    public void setOnDragDropListener(OnDragDropListener listener) {
-        mOnDragDropListener = listener;
-    }
-
+    /**
+     * TODO: This is all swipe to remove code (nothing to do with drag to remove). This should
+     * be cleaned up and removed once drag to remove becomes the only way to remove contacts.
+     */
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
@@ -232,6 +234,10 @@
         requestDisallowInterceptTouchEvent(true);
     }
 
+    /**
+     * End of swipe-to-remove code
+     */
+
     @Override
     public boolean dispatchDragEvent(DragEvent event) {
         final int action = event.getAction();
@@ -239,13 +245,34 @@
         final int eY = (int) event.getY();
         switch (action) {
             case DragEvent.ACTION_DRAG_STARTED:
-                if (!handleDragStarted(mTouchDownForDragStartX, mTouchDownForDragStartY)) {
+                final int[] coordinates = new int[2];
+                getLocationOnScreen(coordinates);
+                // Calculate the X and Y coordinates of the drag event relative to the view
+                final int viewX = eX - coordinates[0];
+                final int viewY = eY - coordinates[1];
+                final View child = getViewAtPosition(viewX, viewY);
+
+                if (!(child instanceof ContactTileRow)) {
+                    // Bail early.
                     return false;
-                };
+                }
+
+                final ContactTileRow tile = (ContactTileRow) child;
+
+                // Disable drag and drop if there is a contact that has been swiped and is currently
+                // in the pending remove state
+                if (tile.getTileAdapter().hasPotentialRemoveEntryIndex()) {
+                    return false;
+                }
+
+                if (!mDragDropController.handleDragStarted(viewX, viewY, tile)) {
+                    return false;
+                }
                 break;
             case DragEvent.ACTION_DRAG_LOCATION:
                 mLastDragY = eY;
-                handleDragHovered(eX, eY);
+                final View view = getViewAtPosition(eX, eY);
+                mDragDropController.handleDragHovered(eX, eY, view);
                 // Kick off {@link #mScrollHandler} if it's not started yet.
                 if (!mIsDragScrollerRunning &&
                         // And if the distance traveled while dragging exceeds the touch slop
@@ -268,13 +295,13 @@
                 mIsDragScrollerRunning = false;
                 // Either a successful drop or it's ended with out drop.
                 if (action == DragEvent.ACTION_DROP || action == DragEvent.ACTION_DRAG_ENDED) {
-                    handleDragFinished(eX, eY);
+                    mDragDropController.handleDragFinished(eX, eY, false);
                 }
                 break;
             default:
                 break;
         }
-        // This ListView will consumer the drag events on behalf of its children.
+        // This ListView will consume the drag events on behalf of its children.
         return true;
     }
 
@@ -303,69 +330,48 @@
         }
     }
 
-    /**
-     * @return True if the drag is started.
-     */
-    private boolean handleDragStarted(int x, int y) {
-        final View child = getViewAtPosition(x, y);
-        if (!(child instanceof ContactTileRow)) {
-            // Bail early.
-            return false;
-        }
-
-        final ContactTileRow tile = (ContactTileRow) child;
-
-        if (tile.getTileAdapter().hasPotentialRemoveEntryIndex()) {
-            return false;
-        }
-
-        final int itemIndex = tile.getItemIndex(x, y);
-        if (itemIndex != -1 && mOnDragDropListener != null) {
-            final PhoneFavoriteTileView tileView =
-                    (PhoneFavoriteTileView) tile.getViewAtPosition(x, y);
-            if (mDragShadowOverlay == null) {
-                return false;
-            }
-
-            mDragShadowOverlay.clearAnimation();
-            mDragShadowBitmap = createDraggedChildBitmap(tileView);
-            if (mDragShadowBitmap == null) {
-                return false;
-            }
-
-            if (tileView instanceof PhoneFavoriteRegularRowView) {
-                mDragShadowLeft = tile.getLeft();
-                mDragShadowTop = tile.getTop();
-            } else {
-                // Square tile is relative to the contact tile,
-                // and contact tile is relative to this list view.
-                mDragShadowLeft = tileView.getLeft() + tileView.getParentRow().getLeft();
-                mDragShadowTop = tileView.getTop() + tileView.getParentRow().getTop();
-            }
-
-            mDragShadowOverlay.setImageBitmap(mDragShadowBitmap);
-            mDragShadowOverlay.setVisibility(VISIBLE);
-            mDragShadowOverlay.setAlpha(DRAG_SHADOW_ALPHA);
-
-            mDragShadowOverlay.setX(mDragShadowLeft);
-            mDragShadowOverlay.setY(mDragShadowTop);
-
-            // x and y passed in are the coordinates of where the user has touched down, calculate
-            // the offset to the top left coordinate of the dragged child.  This will be used for
-            // drawing the drag shadow.
-            mTouchOffsetToChildLeft = x - mDragShadowLeft;
-            mTouchOffsetToChildTop = y - mDragShadowTop;
-
-            // invalidate to trigger a redraw of the drag shadow.
-            invalidate();
-
-            mOnDragDropListener.onDragStarted(itemIndex);
-        }
-
-        return true;
+    public DragDropController getDragDropController() {
+        return mDragDropController;
     }
 
-    private void handleDragHovered(int x, int y) {
+    @Override
+    public void onDragStarted(int itemIndex, int x, int y, PhoneFavoriteTileView tileView) {
+        if (mDragShadowOverlay == null) {
+            return;
+        }
+
+        mDragShadowOverlay.clearAnimation();
+        mDragShadowBitmap = createDraggedChildBitmap(tileView);
+        if (mDragShadowBitmap == null) {
+            return;
+        }
+
+        if (tileView instanceof PhoneFavoriteRegularRowView) {
+            mDragShadowLeft = tileView.getParentRow().getLeft();
+            mDragShadowTop = tileView.getParentRow().getTop();
+        } else {
+            // Square tile is relative to the contact tile,
+            // and contact tile is relative to this list view.
+            mDragShadowLeft = tileView.getLeft() + tileView.getParentRow().getLeft();
+            mDragShadowTop = tileView.getTop() + tileView.getParentRow().getTop();
+        }
+
+        mDragShadowOverlay.setImageBitmap(mDragShadowBitmap);
+        mDragShadowOverlay.setVisibility(VISIBLE);
+        mDragShadowOverlay.setAlpha(DRAG_SHADOW_ALPHA);
+
+        mDragShadowOverlay.setX(mDragShadowLeft);
+        mDragShadowOverlay.setY(mDragShadowTop);
+
+        // x and y passed in are the coordinates of where the user has touched down,
+        // calculate the offset to the top left coordinate of the dragged child.  This
+        // will be used for drawing the drag shadow.
+        mTouchOffsetToChildLeft = x - mDragShadowLeft;
+        mTouchOffsetToChildTop = y - mDragShadowTop;
+    }
+
+    @Override
+    public void onDragHovered(int itemIndex, int x, int y) {
         // Update the drag shadow location.
         mDragShadowLeft = x - mTouchOffsetToChildLeft;
         mDragShadowTop = y - mTouchOffsetToChildTop;
@@ -374,21 +380,10 @@
             mDragShadowOverlay.setX(mDragShadowLeft);
             mDragShadowOverlay.setY(mDragShadowTop);
         }
-
-        final View child = getViewAtPosition(x, y);
-        if (!(child instanceof ContactTileRow)) {
-            // Bail early.
-            return;
-        }
-
-        final ContactTileRow tile = (ContactTileRow) child;
-        final int itemIndex = tile.getItemIndex(x, y);
-        if (itemIndex != -1 && mOnDragDropListener != null) {
-            mOnDragDropListener.onDragHovered(itemIndex);
-        }
     }
 
-    private void handleDragFinished(int x, int y) {
+    @Override
+    public void onDragFinished(int x, int y) {
         // Update the drag shadow location.
         mDragShadowLeft = x - mTouchOffsetToChildLeft;
         mDragShadowTop = y - mTouchOffsetToChildTop;
@@ -400,12 +395,11 @@
                     .setListener(mDragShadowOverAnimatorListener)
                     .start();
         }
-
-        if (mOnDragDropListener != null) {
-            mOnDragDropListener.onDragFinished();
-        }
     }
 
+    @Override
+    public void onDroppedOnRemove() {}
+
     private Bitmap createDraggedChildBitmap(View view) {
         view.setDrawingCacheEnabled(true);
         final Bitmap cache = view.getDrawingCache();
@@ -425,10 +419,4 @@
 
         return bitmap;
     }
-
-    public interface OnDragDropListener {
-        public void onDragStarted(int itemIndex);
-        public void onDragHovered(int itemIndex);
-        public void onDragFinished();
-    }
 }
diff --git a/src/com/android/dialer/list/PhoneFavoritesTileAdapter.java b/src/com/android/dialer/list/PhoneFavoritesTileAdapter.java
index dff68b2..45cc5a3 100644
--- a/src/com/android/dialer/list/PhoneFavoritesTileAdapter.java
+++ b/src/com/android/dialer/list/PhoneFavoritesTileAdapter.java
@@ -59,7 +59,7 @@
  *
  */
 public class PhoneFavoritesTileAdapter extends BaseAdapter implements
-        SwipeHelper.OnItemGestureListener, PhoneFavoriteListView.OnDragDropListener {
+        SwipeHelper.OnItemGestureListener, OnDragDropListener {
     private static final String TAG = PhoneFavoritesTileAdapter.class.getSimpleName();
     private static final boolean DEBUG = false;
 
@@ -1204,24 +1204,38 @@
     }
 
     @Override
-    public void onDragStarted(int itemIndex) {
+    public void onDragStarted(int itemIndex, int x, int y, PhoneFavoriteTileView view) {
         setInDragging(true);
         popContactEntry(itemIndex);
     }
 
     @Override
-    public void onDragHovered(int itemIndex) {
+    public void onDragHovered(int itemIndex, int x, int y) {
         if (mInDragging &&
                 mDragEnteredEntryIndex != itemIndex &&
                 isIndexInBound(itemIndex) &&
-                itemIndex < PIN_LIMIT) {
+                itemIndex < PIN_LIMIT &&
+                itemIndex >= 0) {
             markDropArea(itemIndex);
         }
     }
 
     @Override
-    public void onDragFinished() {
+    public void onDragFinished(int x, int y) {
         setInDragging(false);
-        handleDrop();
+        // A contact has been dragged to the RemoveView in order to be unstarred,  so simply wait
+        // for the new contact cursor which will cause the UI to be refreshed without the unstarred
+        // contact.
+        if (!mAwaitingRemove) {
+            handleDrop();
+        }
+    }
+
+    @Override
+    public void onDroppedOnRemove() {
+        if (mDraggedEntry != null) {
+            unstarAndUnpinContact(mDraggedEntry.lookupKey);
+            mAwaitingRemove = true;
+        }
     }
 }
diff --git a/src/com/android/dialer/list/RemoveView.java b/src/com/android/dialer/list/RemoveView.java
new file mode 100644
index 0000000..16942fe
--- /dev/null
+++ b/src/com/android/dialer/list/RemoveView.java
@@ -0,0 +1,90 @@
+package com.android.dialer.list;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.DragEvent;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.dialer.R;
+
+public class RemoveView extends LinearLayout {
+
+    DragDropController mDragDropController;
+    TextView mRemoveText;
+    ImageView mRemoveIcon;
+    int mUnhighlightedColor;
+    int mHighlightedColor;
+    Drawable mRemoveDrawable;
+    Drawable mRemoveHighlightedDrawable;
+
+    public RemoveView(Context context) {
+      super(context);
+    }
+
+    public RemoveView(Context context, AttributeSet attrs) {
+        this(context, attrs, -1);
+    }
+
+    public RemoveView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        mRemoveText = (TextView) findViewById(R.id.remove_view_text);
+        mRemoveIcon = (ImageView) findViewById(R.id.remove_view_icon);
+        final Resources r = getResources();
+        mUnhighlightedColor = r.getColor(R.color.remove_text_color);
+        mHighlightedColor = r.getColor(R.color.remove_highlighted_text_color);
+        mRemoveDrawable = r.getDrawable(R.drawable.ic_remove);
+        mRemoveHighlightedDrawable = r.getDrawable(R.drawable.ic_remove_highlight);
+    }
+
+    public void setDragDropController(DragDropController controller) {
+        mDragDropController = controller;
+    }
+
+    @Override
+    public boolean dispatchDragEvent(DragEvent event) {
+      final int action = event.getAction();
+      switch (action) {
+        case DragEvent.ACTION_DRAG_ENTERED:
+            setAppearanceHighlighted();
+            break;
+        case DragEvent.ACTION_DRAG_EXITED:
+            setAppearanceNormal();
+            break;
+        case DragEvent.ACTION_DRAG_LOCATION:
+            if (mDragDropController != null) {
+                mDragDropController.handleDragHovered((int) event.getX(),
+                        // the true y-coordinate of the event with respect to the listview is
+                        // offset by the height of the remove view
+                        (int) event.getY() - getHeight(), null);
+            }
+            break;
+        case DragEvent.ACTION_DROP:
+            if (mDragDropController != null) {
+                mDragDropController.handleDragFinished((int) event.getX(), (int) event.getY(), true);
+            }
+            setAppearanceNormal();
+            break;
+      }
+      return true;
+    }
+
+    private void setAppearanceNormal() {
+        mRemoveText.setTextColor(mUnhighlightedColor);
+        mRemoveIcon.setImageDrawable(mRemoveDrawable);
+        invalidate();
+    }
+
+    private void setAppearanceHighlighted() {
+        mRemoveText.setTextColor(mHighlightedColor);
+        mRemoveIcon.setImageDrawable(mRemoveHighlightedDrawable);
+        invalidate();
+    }
+}