Improvement to tap detection for the delete button.

This improvement works as follows:

1) If the user has tapped near the end of the text
      and there is some text near where they tapped that is
      not a chip, don't send the tap to the chip
      and there is no text near where they tapped, send
      the tap to the chip

Change-Id: Ifb93ec716d4a9ff1b9dcc2548465280c39d834cf
diff --git a/src/com/android/ex/chips/RecipientEditTextView.java b/src/com/android/ex/chips/RecipientEditTextView.java
index 2e60760..be4fe0d 100644
--- a/src/com/android/ex/chips/RecipientEditTextView.java
+++ b/src/com/android/ex/chips/RecipientEditTextView.java
@@ -35,9 +35,13 @@
 import android.text.style.ImageSpan;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.view.ActionMode;
 import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ActionMode.Callback;
 import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.ListPopupWindow;
@@ -55,7 +59,7 @@
  * that use the new Chips UI for addressing a message to recipients.
  */
 public class RecipientEditTextView extends MultiAutoCompleteTextView
-    implements OnItemClickListener {
+    implements OnItemClickListener, Callback {
 
     private static final String TAG = "RecipientEditTextView";
 
@@ -93,6 +97,23 @@
         mHandler = new Handler();
         setOnItemClickListener(this);
         mRecipients = new ArrayList<RecipientChip>();
+        setCustomSelectionActionModeCallback(this);
+    }
+
+    @Override
+    public void onSelectionChanged(int start, int end) {
+        // When selection changes, see if it is inside the chips area.
+        // If so, move the cursor back after the chips again.
+        if (mRecipients != null && mRecipients.size() > 0) {
+            Spannable span = getSpannable();
+            RecipientChip[] chips = span.getSpans(start, getText().length(), RecipientChip.class);
+            if (chips != null && chips.length > 0) {
+                // Grab the last chip and set the cursor to after it.
+                setSelection(chips[chips.length - 1].getChipEnd() + 1);
+            }
+        } else {
+            super.onSelectionChanged(start, end);
+        }
     }
 
     public RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed)
@@ -364,18 +385,26 @@
             }
         }
 
-        // If the offset is beyond where there was any visible text,
-        // then leave it should not be pulled into the range of a chip.
-        if (offset > realLength) {
+        // If the offset is beyond or at the end of the text,
+        // leave it alone.
+        if (offset >= realLength) {
             return offset;
         }
-        while (offset >= 0 && findChip(offset) == null) {
+        Editable editable = getText();
+        while (offset >= 0 && (findText(editable, offset) == -1 && findChip(offset) == null)) {
             // Keep walking backward!
             offset--;
         }
         return offset;
     }
 
+    private int findText(Editable text, int offset) {
+        if (text.charAt(offset) != ' ') {
+            return offset;
+        }
+        return -1;
+    }
+
     private RecipientChip findChip(int offset) {
         RecipientChip[] chips = getSpannable().getSpans(0, offset, RecipientChip.class);
         // Find the chip that contains this offset.
@@ -694,5 +723,25 @@
             return mDataId;
         }
     }
+
+    @Override
+    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+        return false;
+    }
+
+    @Override
+    public void onDestroyActionMode(ActionMode mode) {
+    }
+
+    @Override
+    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+        return false;
+    }
+
+    // Prevent selection of chips.
+    @Override
+    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+        return false;
+    }
 }