Use setPressed() for better touch-down behavior

After I59f472a9d864f4abcc4f692fef0a13f004348432, we are able to rely
on setPressed(true) for "touch-down" behavior.

- Rely on setPressed()
- stop overriding shouldDelayChildPressedState() for having system-wide
  delay behavior.
- start sound on setPressed(true)

Bug: 5965380
Bug: 6007908
Bug: 5749440
Change-Id: I5dd440eee1d70992d0db7835c215053904ac3e20
diff --git a/res/layout/dialtacts_activity.xml b/res/layout/dialtacts_activity.xml
index d1af632..7bcddc1 100644
--- a/res/layout/dialtacts_activity.xml
+++ b/res/layout/dialtacts_activity.xml
@@ -20,7 +20,7 @@
     android:layout_marginTop="?android:attr/actionBarSize"
     android:id="@+id/dialtacts_frame"
     >
-    <com.android.contacts.activities.DialtactsViewPager
+    <android.support.v4.view.ViewPager
         android:id="@+id/pager"
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
diff --git a/src/com/android/contacts/activities/DialtactsViewPager.java b/src/com/android/contacts/activities/DialtactsViewPager.java
deleted file mode 100644
index fb869a9..0000000
--- a/src/com/android/contacts/activities/DialtactsViewPager.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.contacts.activities;
-
-import android.content.Context;
-import android.support.v4.view.ViewPager;
-import android.util.AttributeSet;
-
-public class DialtactsViewPager extends ViewPager {
-    public DialtactsViewPager(Context context) {
-        super(context);
-    }
-
-    public DialtactsViewPager(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    /**
-     * ViewPager inherits ViewGroup's default behavior of delayed clicks
-     * on its children, but in order to make the dialpad more responsive we
-     * disable that here. The Call Log and Favorites tabs are both
-     * ListViews which delay their children anyway, as desired to prevent
-     * seeing pressed states flashing while scrolling lists
-     */
-    public boolean shouldDelayChildPressedState() {
-        return false;
-    }
-}
diff --git a/src/com/android/contacts/dialpad/DialpadFragment.java b/src/com/android/contacts/dialpad/DialpadFragment.java
index 02aa2dc..10e59fc 100644
--- a/src/com/android/contacts/dialpad/DialpadFragment.java
+++ b/src/com/android/contacts/dialpad/DialpadFragment.java
@@ -16,7 +16,6 @@
 
 package com.android.contacts.dialpad;
 
-
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
@@ -52,12 +51,12 @@
 import android.text.style.RelativeSizeSpan;
 import android.util.DisplayMetrics;
 import android.util.Log;
+import android.util.SparseArray;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 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;
@@ -87,7 +86,7 @@
         View.OnLongClickListener, View.OnKeyListener,
         AdapterView.OnItemClickListener, TextWatcher,
         PopupMenu.OnMenuItemClickListener,
-        View.OnTouchListener {
+        DialpadImageButton.OnPressedListener {
     private static final String TAG = DialpadFragment.class.getSimpleName();
 
     private static final boolean DEBUG = false;
@@ -96,6 +95,7 @@
 
     /** The length of DTMF tones in milliseconds */
     private static final int TONE_LENGTH_MS = 150;
+    private static final int TONE_LENGTH_INFINITE = -1;
 
     /** The DTMF tone volume relative to other sounds in the stream */
     private static final int TONE_RELATIVE_VOLUME = 80;
@@ -213,6 +213,9 @@
 
     @Override
     public void afterTextChanged(Editable input) {
+        // When DTMF dialpad buttons are being pressed, we delay SpecialCharSequencMgr sequence,
+        // since some of SpecialCharSequenceMgr's behavior is too abrupt for the "touch-down"
+        // behavior.
         if (SpecialCharSequenceMgr.handleChars(getActivity(), input.toString(), mDigits)) {
             // A special sequence was entered, clear the digits
             mDigits.getText().clear();
@@ -465,23 +468,11 @@
     }
 
     private void setupKeypad(View fragmentView) {
-        // 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);
-
-        // Buttons other than numeric ones should use onClick as usual.
-        fragmentView.findViewById(R.id.star).setOnClickListener(this);
-        fragmentView.findViewById(R.id.pound).setOnClickListener(this);
+        int[] buttonIds = new int[] { R.id.one, R.id.two, R.id.three, R.id.four, R.id.five,
+                R.id.six, R.id.seven, R.id.eight, R.id.nine, R.id.zero, R.id.star, R.id.pound};
+        for (int id : buttonIds) {
+            ((DialpadImageButton) fragmentView.findViewById(id)).setOnPressedListener(this);
+        }
 
         // Long-pressing one button will initiate Voicemail.
         fragmentView.findViewById(R.id.one).setOnLongClickListener(this);
@@ -572,6 +563,8 @@
                 (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE);
         telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
 
+        // Make sure we don't leave this activity with a tone still playing.
+        stopTone();
         synchronized (mToneGeneratorLock) {
             if (mToneGenerator != null) {
                 mToneGenerator.release();
@@ -690,6 +683,47 @@
     }
 
     private void keyPressed(int keyCode) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_1:
+                playTone(ToneGenerator.TONE_DTMF_1, TONE_LENGTH_INFINITE);
+                break;
+            case KeyEvent.KEYCODE_2:
+                playTone(ToneGenerator.TONE_DTMF_2, TONE_LENGTH_INFINITE);
+                break;
+            case KeyEvent.KEYCODE_3:
+                playTone(ToneGenerator.TONE_DTMF_3, TONE_LENGTH_INFINITE);
+                break;
+            case KeyEvent.KEYCODE_4:
+                playTone(ToneGenerator.TONE_DTMF_4, TONE_LENGTH_INFINITE);
+                break;
+            case KeyEvent.KEYCODE_5:
+                playTone(ToneGenerator.TONE_DTMF_5, TONE_LENGTH_INFINITE);
+                break;
+            case KeyEvent.KEYCODE_6:
+                playTone(ToneGenerator.TONE_DTMF_6, TONE_LENGTH_INFINITE);
+                break;
+            case KeyEvent.KEYCODE_7:
+                playTone(ToneGenerator.TONE_DTMF_7, TONE_LENGTH_INFINITE);
+                break;
+            case KeyEvent.KEYCODE_8:
+                playTone(ToneGenerator.TONE_DTMF_8, TONE_LENGTH_INFINITE);
+                break;
+            case KeyEvent.KEYCODE_9:
+                playTone(ToneGenerator.TONE_DTMF_9, TONE_LENGTH_INFINITE);
+                break;
+            case KeyEvent.KEYCODE_0:
+                playTone(ToneGenerator.TONE_DTMF_0, TONE_LENGTH_INFINITE);
+                break;
+            case KeyEvent.KEYCODE_POUND:
+                playTone(ToneGenerator.TONE_DTMF_P, TONE_LENGTH_INFINITE);
+                break;
+            case KeyEvent.KEYCODE_STAR:
+                playTone(ToneGenerator.TONE_DTMF_S, TONE_LENGTH_INFINITE);
+                break;
+            default:
+                break;
+        }
+
         mHaptic.vibrate();
         KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
         mDigits.onKeyDown(keyCode, event);
@@ -715,12 +749,15 @@
     }
 
     /**
-     * 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...)
+     * When a key is pressed, we start playing DTMF tone, do vibration, and enter the digit
+     * immediately. When a key is released, we stop the tone. Note that the "key press" event will
+     * be delivered by the system with certain amount of delay, it won't be synced with user's
+     * actual "touch-down" behavior.
      */
     @Override
-    public boolean onTouch(View view, MotionEvent event) {
-        if (event.getAction() == MotionEvent.ACTION_DOWN) {
+    public void onPressed(View view, boolean pressed) {
+        if (DEBUG) Log.d(TAG, "onPressed(). view: " + view + ", pressed: " + pressed);
+        if (pressed) {
             switch (view.getId()) {
                 case R.id.one: {
                     keyPressed(KeyEvent.KEYCODE_1);
@@ -762,82 +799,28 @@
                     keyPressed(KeyEvent.KEYCODE_0);
                     break;
                 }
+                case R.id.pound: {
+                    keyPressed(KeyEvent.KEYCODE_POUND);
+                    break;
+                }
+                case R.id.star: {
+                    keyPressed(KeyEvent.KEYCODE_STAR);
+                    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();
+        } else {
+            view.jumpDrawablesToCurrentState();
+            stopTone();
         }
-        return false;
     }
 
     @Override
     public void onClick(View view) {
         switch (view.getId()) {
-            case R.id.pound: {
-                playTone(ToneGenerator.TONE_DTMF_P);
-                keyPressed(KeyEvent.KEYCODE_POUND);
-                return;
-            }
-            case R.id.star: {
-                playTone(ToneGenerator.TONE_DTMF_S);
-                keyPressed(KeyEvent.KEYCODE_STAR);
-                return;
-            }
             case R.id.deleteButton: {
                 keyPressed(KeyEvent.KEYCODE_DEL);
                 return;
@@ -1090,14 +1073,25 @@
 
     /**
      * Plays the specified tone for TONE_LENGTH_MS milliseconds.
+     */
+    private void playTone(int tone) {
+        playTone(tone, TONE_LENGTH_MS);
+    }
+
+    /**
+     * Play the specified tone for the specified milliseconds
      *
      * The tone is played locally, using the audio stream for phone calls.
      * Tones are played only if the "Audible touch tones" user preference
      * is checked, and are NOT played if the device is in silent mode.
      *
+     * The tone length can be -1, meaning "keep playing the tone." If the caller does so, it should
+     * call stopTone() afterward.
+     *
      * @param tone a tone code from {@link ToneGenerator}
+     * @param durationMs tone length.
      */
-    void playTone(int tone) {
+    private void playTone(int tone, int durationMs) {
         // if local tone playback is disabled, just return.
         if (!mDTMFToneEnabled) {
             return;
@@ -1123,7 +1117,24 @@
             }
 
             // Start the new tone (will stop any playing tone)
-            mToneGenerator.startTone(tone, TONE_LENGTH_MS);
+            mToneGenerator.startTone(tone, durationMs);
+        }
+    }
+
+    /**
+     * Stop the tone if it is played.
+     */
+    private void stopTone() {
+        // if local tone playback is disabled, just return.
+        if (!mDTMFToneEnabled) {
+            return;
+        }
+        synchronized (mToneGeneratorLock) {
+            if (mToneGenerator == null) {
+                Log.w(TAG, "stopTone: mToneGenerator == null");
+                return;
+            }
+            mToneGenerator.stopTone();
         }
     }
 
diff --git a/src/com/android/contacts/dialpad/DialpadImageButton.java b/src/com/android/contacts/dialpad/DialpadImageButton.java
index 6e01379..a18cbb8 100644
--- a/src/com/android/contacts/dialpad/DialpadImageButton.java
+++ b/src/com/android/contacts/dialpad/DialpadImageButton.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
+import android.view.View;
 import android.widget.ImageButton;
 
 /**
@@ -29,6 +30,15 @@
  * the behavior.
  */
 public class DialpadImageButton extends ImageButton {
+    public interface OnPressedListener {
+        public void onPressed(View view, boolean pressed);
+    }
+
+    private OnPressedListener mOnPressedListener;
+
+    public void setOnPressedListener(OnPressedListener onPressedListener) {
+        mOnPressedListener = onPressedListener;
+    }
 
     public DialpadImageButton(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -39,11 +49,10 @@
     }
 
     @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        final boolean ret = super.onTouchEvent(event);
-        if (event.getAction() == MotionEvent.ACTION_CANCEL) {
-            jumpDrawablesToCurrentState();
+    public void setPressed(boolean pressed) {
+        super.setPressed(pressed);
+        if (mOnPressedListener != null) {
+            mOnPressedListener.onPressed(this, pressed);
         }
-        return ret;
     }
 }