diff options
Diffstat (limited to 'java/com/android/dialer/dialpadview/DialpadKeyButton.java')
-rw-r--r-- | java/com/android/dialer/dialpadview/DialpadKeyButton.java | 231 |
1 files changed, 231 insertions, 0 deletions
diff --git a/java/com/android/dialer/dialpadview/DialpadKeyButton.java b/java/com/android/dialer/dialpadview/DialpadKeyButton.java new file mode 100644 index 000000000..24ca9cc86 --- /dev/null +++ b/java/com/android/dialer/dialpadview/DialpadKeyButton.java @@ -0,0 +1,231 @@ +/* + * 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.dialer.dialpadview; + +import android.content.Context; +import android.graphics.RectF; +import android.os.Bundle; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.AccessibilityNodeInfo; +import android.widget.FrameLayout; + +/** + * Custom class for dialpad buttons. + * + * <p>When touch exploration mode is enabled for accessibility, this class implements the + * lift-to-type interaction model: + * + * <ul> + * <li>Hovering over the button will cause it to gain accessibility focus + * <li>Removing the hover pointer while inside the bounds of the button will perform a click action + * <li>If long-click is supported, hovering over the button for a longer period of time will switch + * to the long-click action + * <li>Moving the hover pointer outside of the bounds of the button will restore to the normal click + * action + * <ul> + */ +public class DialpadKeyButton extends FrameLayout { + + /** Timeout before switching to long-click accessibility mode. */ + private static final int LONG_HOVER_TIMEOUT = ViewConfiguration.getLongPressTimeout() * 2; + + /** Accessibility manager instance used to check touch exploration state. */ + private AccessibilityManager mAccessibilityManager; + + /** Bounds used to filter HOVER_EXIT events. */ + private RectF mHoverBounds = new RectF(); + + /** Whether this view is currently in the long-hover state. */ + private boolean mLongHovered; + + /** Alternate content description for long-hover state. */ + private CharSequence mLongHoverContentDesc; + + /** Backup of standard content description. Used for accessibility. */ + private CharSequence mBackupContentDesc; + + /** Backup of clickable property. Used for accessibility. */ + private boolean mWasClickable; + + /** Backup of long-clickable property. Used for accessibility. */ + private boolean mWasLongClickable; + + /** Runnable used to trigger long-click mode for accessibility. */ + private Runnable mLongHoverRunnable; + + private OnPressedListener mOnPressedListener; + + public DialpadKeyButton(Context context, AttributeSet attrs) { + super(context, attrs); + initForAccessibility(context); + } + + public DialpadKeyButton(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initForAccessibility(context); + } + + public void setOnPressedListener(OnPressedListener onPressedListener) { + mOnPressedListener = onPressedListener; + } + + private void initForAccessibility(Context context) { + mAccessibilityManager = + (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); + } + + public void setLongHoverContentDescription(CharSequence contentDescription) { + mLongHoverContentDesc = contentDescription; + + if (mLongHovered) { + super.setContentDescription(mLongHoverContentDesc); + } + } + + @Override + public void setContentDescription(CharSequence contentDescription) { + if (mLongHovered) { + mBackupContentDesc = contentDescription; + } else { + super.setContentDescription(contentDescription); + } + } + + @Override + public void setPressed(boolean pressed) { + super.setPressed(pressed); + if (mOnPressedListener != null) { + 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 performAccessibilityAction(int action, Bundle arguments) { + if (action == AccessibilityNodeInfo.ACTION_CLICK) { + simulateClickForAccessibility(); + return true; + } + + return super.performAccessibilityAction(action, arguments); + } + + @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. + mWasClickable = isClickable(); + mWasLongClickable = isLongClickable(); + if (mWasLongClickable && mLongHoverContentDesc != null) { + if (mLongHoverRunnable == null) { + mLongHoverRunnable = + new Runnable() { + @Override + public void run() { + setLongHovered(true); + announceForAccessibility(mLongHoverContentDesc); + } + }; + } + postDelayed(mLongHoverRunnable, LONG_HOVER_TIMEOUT); + } + + setClickable(false); + setLongClickable(false); + break; + case MotionEvent.ACTION_HOVER_EXIT: + if (mHoverBounds.contains(event.getX(), event.getY())) { + if (mLongHovered) { + performLongClick(); + } else { + simulateClickForAccessibility(); + } + } + + cancelLongHover(); + setClickable(mWasClickable); + setLongClickable(mWasLongClickable); + break; + } + } + + return super.onHoverEvent(event); + } + + /** + * When accessibility is on, simulate press and release to preserve the semantic meaning of + * performClick(). Required for Braille support. + */ + private void simulateClickForAccessibility() { + // Checking the press state prevents double activation. + if (isPressed()) { + return; + } + + setPressed(true); + + // Stay consistent with performClick() by sending the event after + // setting the pressed state but before performing the action. + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); + + setPressed(false); + } + + private void setLongHovered(boolean enabled) { + if (mLongHovered != enabled) { + mLongHovered = enabled; + + // Switch between normal and alternate description, if available. + if (enabled) { + mBackupContentDesc = getContentDescription(); + super.setContentDescription(mLongHoverContentDesc); + } else { + super.setContentDescription(mBackupContentDesc); + } + } + } + + private void cancelLongHover() { + if (mLongHoverRunnable != null) { + removeCallbacks(mLongHoverRunnable); + } + setLongHovered(false); + } + + public interface OnPressedListener { + + void onPressed(View view, boolean pressed); + } +} |