Let numeric buttons rely more on touch-down on dialpad

This change lets the dialpad shows tentative digit during touch-down.
Because of some other problems we're still using touch-up for
emitting DTMF sound.

TESTED:
- Call usual phone numbers
- Try basic swipe and see a tentative number is erased
-- (e.g. Press '5' and start swiping, seeing '5' being erased during
    the horizontal swipe). User should not hear DTMF sound.
- Try Voicemail by long-pressing '1'.
- Try entering '+' by long-pressing '0'. It should remove tentative
  '0'.

Bug: 5749440
Change-Id: Id5332e643a6476efceb044f7d372118ffdc77377
diff --git a/res/layout/dialpad.xml b/res/layout/dialpad.xml
index 45f40f6..247bd5a 100644
--- a/res/layout/dialpad.xml
+++ b/res/layout/dialpad.xml
@@ -31,13 +31,16 @@
     <TableRow
          android:layout_height="0px"
          android:layout_weight="1">
-        <ImageButton android:id="@+id/one" style="@style/DialtactsDialpadButtonStyle"
+        <com.android.contacts.dialpad.DialpadImageButton
+            android:id="@+id/one" style="@style/DialtactsDialpadButtonStyle"
             android:src="@drawable/dial_num_1"
             android:contentDescription="@string/description_image_button_one" />
-        <ImageButton android:id="@+id/two" style="@style/DialtactsDialpadButtonStyle"
+        <com.android.contacts.dialpad.DialpadImageButton
+            android:id="@+id/two" style="@style/DialtactsDialpadButtonStyle"
             android:src="@drawable/dial_num_2"
             android:contentDescription="@string/description_image_button_two" />
-        <ImageButton android:id="@+id/three" style="@style/DialtactsDialpadButtonStyle"
+        <com.android.contacts.dialpad.DialpadImageButton
+            android:id="@+id/three" style="@style/DialtactsDialpadButtonStyle"
             android:src="@drawable/dial_num_3"
             android:contentDescription="@string/description_image_button_three" />
     </TableRow>
@@ -45,13 +48,16 @@
     <TableRow
          android:layout_height="0px"
          android:layout_weight="1">
-        <ImageButton android:id="@+id/four" style="@style/DialtactsDialpadButtonStyle"
+        <com.android.contacts.dialpad.DialpadImageButton
+            android:id="@+id/four" style="@style/DialtactsDialpadButtonStyle"
             android:src="@drawable/dial_num_4"
             android:contentDescription="@string/description_image_button_four" />
-        <ImageButton android:id="@+id/five" style="@style/DialtactsDialpadButtonStyle"
+        <com.android.contacts.dialpad.DialpadImageButton
+            android:id="@+id/five" style="@style/DialtactsDialpadButtonStyle"
             android:src="@drawable/dial_num_5"
             android:contentDescription="@string/description_image_button_five" />
-        <ImageButton android:id="@+id/six" style="@style/DialtactsDialpadButtonStyle"
+        <com.android.contacts.dialpad.DialpadImageButton
+            android:id="@+id/six" style="@style/DialtactsDialpadButtonStyle"
             android:src="@drawable/dial_num_6"
             android:contentDescription="@string/description_image_button_six" />
     </TableRow>
@@ -59,13 +65,16 @@
     <TableRow
          android:layout_height="0px"
          android:layout_weight="1">
-        <ImageButton android:id="@+id/seven" style="@style/DialtactsDialpadButtonStyle"
+        <com.android.contacts.dialpad.DialpadImageButton
+            android:id="@+id/seven" style="@style/DialtactsDialpadButtonStyle"
             android:src="@drawable/dial_num_7"
             android:contentDescription="@string/description_image_button_seven" />
-        <ImageButton android:id="@+id/eight" style="@style/DialtactsDialpadButtonStyle"
+        <com.android.contacts.dialpad.DialpadImageButton
+            android:id="@+id/eight" style="@style/DialtactsDialpadButtonStyle"
             android:src="@drawable/dial_num_8"
             android:contentDescription="@string/description_image_button_eight" />
-        <ImageButton android:id="@+id/nine" style="@style/DialtactsDialpadButtonStyle"
+        <com.android.contacts.dialpad.DialpadImageButton
+            android:id="@+id/nine" style="@style/DialtactsDialpadButtonStyle"
             android:src="@drawable/dial_num_9"
             android:contentDescription="@string/description_image_button_nine" />
     </TableRow>
@@ -73,13 +82,16 @@
     <TableRow
          android:layout_height="0px"
          android:layout_weight="1">
-        <ImageButton android:id="@+id/star" style="@style/DialtactsDialpadButtonStyle"
+        <com.android.contacts.dialpad.DialpadImageButton
+            android:id="@+id/star" style="@style/DialtactsDialpadButtonStyle"
             android:src="@drawable/dial_num_star"
             android:contentDescription="@string/description_image_button_star" />
-        <ImageButton android:id="@+id/zero" style="@style/DialtactsDialpadButtonStyle"
+        <com.android.contacts.dialpad.DialpadImageButton
+            android:id="@+id/zero" style="@style/DialtactsDialpadButtonStyle"
             android:src="@drawable/dial_num_0"
             android:contentDescription="@string/description_image_button_zero" />
-        <ImageButton android:id="@+id/pound" style="@style/DialtactsDialpadButtonStyle"
+        <com.android.contacts.dialpad.DialpadImageButton
+            android:id="@+id/pound" style="@style/DialtactsDialpadButtonStyle"
             android:src="@drawable/dial_num_pound"
             android:contentDescription="@string/description_image_button_pound" />
     </TableRow>
diff --git a/src/com/android/contacts/dialpad/DialpadFragment.java b/src/com/android/contacts/dialpad/DialpadFragment.java
index 49de9d7..c8e4d62 100644
--- a/src/com/android/contacts/dialpad/DialpadFragment.java
+++ b/src/com/android/contacts/dialpad/DialpadFragment.java
@@ -64,6 +64,7 @@
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
@@ -82,7 +83,8 @@
         implements View.OnClickListener,
         View.OnLongClickListener, View.OnKeyListener,
         AdapterView.OnItemClickListener, TextWatcher,
-        PopupMenu.OnMenuItemClickListener {
+        PopupMenu.OnMenuItemClickListener,
+        View.OnTouchListener {
     private static final String TAG = DialpadFragment.class.getSimpleName();
 
     private static final String EMPTY_NUMBER = "";
@@ -151,7 +153,7 @@
      * TODO: Keep in sync with the string defined in OutgoingCallBroadcaster.java
      * in Phone app until this is replaced with the ITelephony API.
      */
-    static final String EXTRA_SEND_EMPTY_FLASH
+    private static final String EXTRA_SEND_EMPTY_FLASH
             = "com.android.phone.extra.SEND_EMPTY_FLASH";
 
     private String mCurrentCountryIso;
@@ -444,26 +446,30 @@
     }
 
     private void setupKeypad(View fragmentView) {
-        // Setup the listeners for the buttons
-        View view = fragmentView.findViewById(R.id.one);
-        view.setOnClickListener(this);
-        view.setOnLongClickListener(this);
+        // For numeric buttons, we rely on onTouchListener instead of onClickListener
+        // for faster event handling, while some other buttons since basically
+        // onTouch event conflicts with horizontal swipes.
+        fragmentView.findViewById(R.id.one).setOnTouchListener(this);
+        fragmentView.findViewById(R.id.two).setOnTouchListener(this);
+        fragmentView.findViewById(R.id.three).setOnTouchListener(this);
+        fragmentView.findViewById(R.id.four).setOnTouchListener(this);
+        fragmentView.findViewById(R.id.five).setOnTouchListener(this);
+        fragmentView.findViewById(R.id.six).setOnTouchListener(this);
+        fragmentView.findViewById(R.id.seven).setOnTouchListener(this);
+        fragmentView.findViewById(R.id.eight).setOnTouchListener(this);
+        fragmentView.findViewById(R.id.nine).setOnTouchListener(this);
+        fragmentView.findViewById(R.id.zero).setOnTouchListener(this);
 
-        fragmentView.findViewById(R.id.two).setOnClickListener(this);
-        fragmentView.findViewById(R.id.three).setOnClickListener(this);
-        fragmentView.findViewById(R.id.four).setOnClickListener(this);
-        fragmentView.findViewById(R.id.five).setOnClickListener(this);
-        fragmentView.findViewById(R.id.six).setOnClickListener(this);
-        fragmentView.findViewById(R.id.seven).setOnClickListener(this);
-        fragmentView.findViewById(R.id.eight).setOnClickListener(this);
-        fragmentView.findViewById(R.id.nine).setOnClickListener(this);
+        // Buttons other than numeric ones should use onClick as usual.
         fragmentView.findViewById(R.id.star).setOnClickListener(this);
-
-        view = fragmentView.findViewById(R.id.zero);
-        view.setOnClickListener(this);
-        view.setOnLongClickListener(this);
-
         fragmentView.findViewById(R.id.pound).setOnClickListener(this);
+
+        // Long-pressing one button will initiate Voicemail.
+        fragmentView.findViewById(R.id.one).setOnLongClickListener(this);
+
+        // Long-pressing zero button will enter '+' instead.
+        fragmentView.findViewById(R.id.zero).setOnLongClickListener(this);
+
     }
 
     @Override
@@ -680,59 +686,120 @@
         return false;
     }
 
+    /**
+     * We handle the key based on the DOWN event, but we wait till the UP event to play the local
+     * DTMF tone (to avoid playing a spurious tone if the user is actually doing a swipe...)
+     */
+    @Override
+    public boolean onTouch(View view, MotionEvent event) {
+        if (event.getAction() == MotionEvent.ACTION_DOWN) {
+            switch (view.getId()) {
+                case R.id.one: {
+                    keyPressed(KeyEvent.KEYCODE_1);
+                    break;
+                }
+                case R.id.two: {
+                    keyPressed(KeyEvent.KEYCODE_2);
+                    break;
+                }
+                case R.id.three: {
+                    keyPressed(KeyEvent.KEYCODE_3);
+                    break;
+                }
+                case R.id.four: {
+                    keyPressed(KeyEvent.KEYCODE_4);
+                    break;
+                }
+                case R.id.five: {
+                    keyPressed(KeyEvent.KEYCODE_5);
+                    break;
+                }
+                case R.id.six: {
+                    keyPressed(KeyEvent.KEYCODE_6);
+                    break;
+                }
+                case R.id.seven: {
+                    keyPressed(KeyEvent.KEYCODE_7);
+                    break;
+                }
+                case R.id.eight: {
+                    keyPressed(KeyEvent.KEYCODE_8);
+                    break;
+                }
+                case R.id.nine: {
+                    keyPressed(KeyEvent.KEYCODE_9);
+                    break;
+                }
+                case R.id.zero: {
+                    keyPressed(KeyEvent.KEYCODE_0);
+                    break;
+                }
+                default: {
+                    Log.wtf(TAG, "Unexpected onTouch(ACTION_DOWN) event from: " + view);
+                    break;
+                }
+            }
+        } else if (event.getAction() == MotionEvent.ACTION_UP) {
+            switch (view.getId()) {
+                case R.id.one: {
+                    playTone(ToneGenerator.TONE_DTMF_1);
+                    break;
+                }
+                case R.id.two: {
+                    playTone(ToneGenerator.TONE_DTMF_2);
+                    break;
+                }
+                case R.id.three: {
+                    playTone(ToneGenerator.TONE_DTMF_3);
+                    break;
+                }
+                case R.id.four: {
+                    playTone(ToneGenerator.TONE_DTMF_4);
+                    break;
+                }
+                case R.id.five: {
+                    playTone(ToneGenerator.TONE_DTMF_5);
+                    break;
+                }
+                case R.id.six: {
+                    playTone(ToneGenerator.TONE_DTMF_6);
+                    break;
+                }
+                case R.id.seven: {
+                    playTone(ToneGenerator.TONE_DTMF_7);
+                    break;
+                }
+                case R.id.eight: {
+                    playTone(ToneGenerator.TONE_DTMF_8);
+                    break;
+                }
+                case R.id.nine: {
+                    playTone(ToneGenerator.TONE_DTMF_9);
+                    break;
+                }
+                case R.id.zero: {
+                    playTone(ToneGenerator.TONE_DTMF_0);
+                    break;
+                }
+                default: {
+                    Log.wtf(TAG, "Unexpected onTouch(ACTION_UP) event from: " + view);
+                    break;
+                }
+            }
+        } else if (event.getAction() == MotionEvent.ACTION_CANCEL) {
+            // This event will be thrown when a user starts dragging the dialpad screen,
+            // intending horizontal swipe. The system will see the event after ACTION_DOWN while
+            // it won't see relevant ACTION_UP event anymore.
+            //
+            // Here, remove the last digit already entered in the last ACTION_DOWN event.
+            removeLastOneDigitIfPossible();
+        }
+        return false;
+    }
+
     @Override
     public void onClick(View view) {
         switch (view.getId()) {
-            case R.id.one: {
-                playTone(ToneGenerator.TONE_DTMF_1);
-                keyPressed(KeyEvent.KEYCODE_1);
-                return;
-            }
-            case R.id.two: {
-                playTone(ToneGenerator.TONE_DTMF_2);
-                keyPressed(KeyEvent.KEYCODE_2);
-                return;
-            }
-            case R.id.three: {
-                playTone(ToneGenerator.TONE_DTMF_3);
-                keyPressed(KeyEvent.KEYCODE_3);
-                return;
-            }
-            case R.id.four: {
-                playTone(ToneGenerator.TONE_DTMF_4);
-                keyPressed(KeyEvent.KEYCODE_4);
-                return;
-            }
-            case R.id.five: {
-                playTone(ToneGenerator.TONE_DTMF_5);
-                keyPressed(KeyEvent.KEYCODE_5);
-                return;
-            }
-            case R.id.six: {
-                playTone(ToneGenerator.TONE_DTMF_6);
-                keyPressed(KeyEvent.KEYCODE_6);
-                return;
-            }
-            case R.id.seven: {
-                playTone(ToneGenerator.TONE_DTMF_7);
-                keyPressed(KeyEvent.KEYCODE_7);
-                return;
-            }
-            case R.id.eight: {
-                playTone(ToneGenerator.TONE_DTMF_8);
-                keyPressed(KeyEvent.KEYCODE_8);
-                return;
-            }
-            case R.id.nine: {
-                playTone(ToneGenerator.TONE_DTMF_9);
-                keyPressed(KeyEvent.KEYCODE_9);
-                return;
-            }
-            case R.id.zero: {
-                playTone(ToneGenerator.TONE_DTMF_0);
-                keyPressed(KeyEvent.KEYCODE_0);
-                return;
-            }
             case R.id.pound: {
                 playTone(ToneGenerator.TONE_DTMF_P);
                 keyPressed(KeyEvent.KEYCODE_POUND);
@@ -770,6 +837,11 @@
                 if (popup != null) {
                     popup.show();
                 }
+                return;
+            }
+            default: {
+                Log.wtf(TAG, "Unexpected onClick() event from: " + view);
+                return;
             }
         }
     }
@@ -790,7 +862,7 @@
     @Override
     public boolean onLongClick(View view) {
         final Editable digits = mDigits.getText();
-        int id = view.getId();
+        final int id = view.getId();
         switch (id) {
             case R.id.deleteButton: {
                 digits.clear();
@@ -801,7 +873,12 @@
                 return true;
             }
             case R.id.one: {
-                if (isDigitsEmpty()) {
+                // '1' may be already entered since we rely on onTouch() event for numeric buttons.
+                // Just for safety we also check if the digits field is empty or not.
+                if (isDigitsEmpty() || TextUtils.equals(mDigits.getText(), "1")) {
+                    // We'll try to initiate voicemail and thus we want to remove irrelevant string.
+                    removeLastOneDigitIfPossible();
+
                     if (isVoicemailAvailable()) {
                         callVoicemail();
                     } else if (getActivity() != null) {
@@ -815,6 +892,8 @@
                 return false;
             }
             case R.id.zero: {
+                // Remove tentative input ('0') done by onTouch().
+                removeLastOneDigitIfPossible();
                 keyPressed(KeyEvent.KEYCODE_PLUS);
                 return true;
             }
@@ -829,6 +908,14 @@
         return false;
     }
 
+    private void removeLastOneDigitIfPossible() {
+        final Editable editable = mDigits.getText();
+        final int length = editable.length();
+        if (length > 0) {
+            mDigits.getText().delete(length - 1, length);
+        }
+    }
+
     public void callVoicemail() {
         startActivity(ContactsUtils.getVoicemailIntent());
         mDigits.getText().clear(); // TODO: Fix bug 1745781
diff --git a/src/com/android/contacts/dialpad/DialpadImageButton.java b/src/com/android/contacts/dialpad/DialpadImageButton.java
new file mode 100644
index 0000000..6e01379
--- /dev/null
+++ b/src/com/android/contacts/dialpad/DialpadImageButton.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.dialpad;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.ImageButton;
+
+/**
+ * Custom {@link ImageButton} for dialpad buttons.
+ *
+ * During horizontal swipe, we want to exit "fading out" animation offered by its background
+ * just after starting the swipe.This class overrides {@link #onTouchEvent(MotionEvent)} to achieve
+ * the behavior.
+ */
+public class DialpadImageButton extends ImageButton {
+
+    public DialpadImageButton(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public DialpadImageButton(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        final boolean ret = super.onTouchEvent(event);
+        if (event.getAction() == MotionEvent.ACTION_CANCEL) {
+            jumpDrawablesToCurrentState();
+        }
+        return ret;
+    }
+}