Implement list-to-type for phone dialer accessibility

Cherry pick of  I7d4a292ebc306b51666d9aa840ddcb2a7e5770fb from jb-mr1

bug: 7297096
Change-Id: I6d95d5d6e2f9ef320002b344af82c9c7a38ffde8
diff --git a/src/com/android/dialer/dialpad/DialpadImageButton.java b/src/com/android/dialer/dialpad/DialpadImageButton.java
index d5f825b..5512a0c 100644
--- a/src/com/android/dialer/dialpad/DialpadImageButton.java
+++ b/src/com/android/dialer/dialpad/DialpadImageButton.java
@@ -17,19 +17,26 @@
 package com.android.dialer.dialpad;
 
 import android.content.Context;
+import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.accessibility.AccessibilityManager;
 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.
+ * <p>
+ * This class implements lift-to-type interaction when touch exploration is
+ * enabled.
  */
 public class DialpadImageButton extends ImageButton {
+    /** Accessibility manager instance used to check touch exploration state. */
+    private AccessibilityManager mAccessibilityManager;
+
+    /** Bounds used to filter HOVER_EXIT events. */
+    private Rect mHoverBounds = new Rect();
+
     public interface OnPressedListener {
         public void onPressed(View view, boolean pressed);
     }
@@ -42,10 +49,17 @@
 
     public DialpadImageButton(Context context, AttributeSet attrs) {
         super(context, attrs);
+        initForAccessibility(context);
     }
 
     public DialpadImageButton(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
+        initForAccessibility(context);
+    }
+
+    private void initForAccessibility(Context context) {
+        mAccessibilityManager = (AccessibilityManager) context.getSystemService(
+                Context.ACCESSIBILITY_SERVICE);
     }
 
     @Override
@@ -55,4 +69,54 @@
             mOnPressedListener.onPressed(this, pressed);
         }
     }
+
+    @Override
+    public void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+
+        mHoverBounds.left = getPaddingLeft();
+        mHoverBounds.right = w - getPaddingRight();
+        mHoverBounds.top = getPaddingTop();
+        mHoverBounds.bottom = h - getPaddingBottom();
+    }
+
+    @Override
+    public boolean performClick() {
+        // When accessibility is on, simulate press and release to preserve the
+        // semantic meaning of performClick(). Required for Braille support.
+        if (mAccessibilityManager.isEnabled()) {
+            // Checking the press state prevents double activation.
+            if (!isPressed()) {
+                setPressed(true);
+                setPressed(false);
+            }
+
+            return true;
+        }
+
+        return super.performClick();
+    }
+
+    @Override
+    public boolean onHoverEvent(MotionEvent event) {
+        // When touch exploration is turned on, lifting a finger while inside
+        // the button's hover target bounds should perform a click action.
+        if (mAccessibilityManager.isEnabled()
+                && mAccessibilityManager.isTouchExplorationEnabled()) {
+            switch (event.getActionMasked()) {
+                case MotionEvent.ACTION_HOVER_ENTER:
+                    // Lift-to-type temporarily disables double-tap activation.
+                    setClickable(false);
+                    break;
+                case MotionEvent.ACTION_HOVER_EXIT:
+                    if (mHoverBounds.contains((int) event.getX(), (int) event.getY())) {
+                        performClick();
+                    }
+                    setClickable(true);
+                    break;
+            }
+        }
+
+        return super.onHoverEvent(event);
+    }
 }