diff options
Diffstat (limited to 'java/com/android/dialer/dialpadview/DialpadView.java')
-rw-r--r-- | java/com/android/dialer/dialpadview/DialpadView.java | 464 |
1 files changed, 464 insertions, 0 deletions
diff --git a/java/com/android/dialer/dialpadview/DialpadView.java b/java/com/android/dialer/dialpadview/DialpadView.java new file mode 100644 index 000000000..4a9b500b7 --- /dev/null +++ b/java/com/android/dialer/dialpadview/DialpadView.java @@ -0,0 +1,464 @@ +/* + * Copyright (C) 2014 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.animation.AnimatorListenerAdapter; +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.RippleDrawable; +import android.os.Build; +import android.text.Spannable; +import android.text.TextUtils; +import android.text.style.TtsSpan; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewPropertyAnimator; +import android.view.accessibility.AccessibilityManager; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.TextView; +import com.android.dialer.animation.AnimUtils; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Locale; + +/** View that displays a twelve-key phone dialpad. */ +public class DialpadView extends LinearLayout { + + private static final String TAG = DialpadView.class.getSimpleName(); + + private static final double DELAY_MULTIPLIER = 0.66; + private static final double DURATION_MULTIPLIER = 0.8; + // For animation. + private static final int KEY_FRAME_DURATION = 33; + /** {@code True} if the dialpad is in landscape orientation. */ + private final boolean mIsLandscape; + /** {@code True} if the dialpad is showing in a right-to-left locale. */ + private final boolean mIsRtl; + + private final int[] mButtonIds = + new int[] { + R.id.zero, + 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.star, + R.id.pound + }; + private EditText mDigits; + private ImageButton mDelete; + private View mOverflowMenuButton; + private ColorStateList mRippleColor; + private ViewGroup mRateContainer; + private TextView mIldCountry; + private TextView mIldRate; + private boolean mCanDigitsBeEdited; + private int mTranslateDistance; + + public DialpadView(Context context) { + this(context, null); + } + + public DialpadView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public DialpadView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Dialpad); + mRippleColor = a.getColorStateList(R.styleable.Dialpad_dialpad_key_button_touch_tint); + a.recycle(); + + mTranslateDistance = + getResources().getDimensionPixelSize(R.dimen.dialpad_key_button_translate_y); + + mIsLandscape = + getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; + mIsRtl = + TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL; + } + + @Override + protected void onFinishInflate() { + setupKeypad(); + mDigits = (EditText) findViewById(R.id.digits); + mDelete = (ImageButton) findViewById(R.id.deleteButton); + mOverflowMenuButton = findViewById(R.id.dialpad_overflow); + mRateContainer = (ViewGroup) findViewById(R.id.rate_container); + mIldCountry = (TextView) mRateContainer.findViewById(R.id.ild_country); + mIldRate = (TextView) mRateContainer.findViewById(R.id.ild_rate); + + AccessibilityManager accessibilityManager = + (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); + if (accessibilityManager.isEnabled()) { + // The text view must be selected to send accessibility events. + mDigits.setSelected(true); + } + } + + private void setupKeypad() { + final int[] letterIds = + new int[] { + R.string.dialpad_0_letters, + R.string.dialpad_1_letters, + R.string.dialpad_2_letters, + R.string.dialpad_3_letters, + R.string.dialpad_4_letters, + R.string.dialpad_5_letters, + R.string.dialpad_6_letters, + R.string.dialpad_7_letters, + R.string.dialpad_8_letters, + R.string.dialpad_9_letters, + R.string.dialpad_star_letters, + R.string.dialpad_pound_letters + }; + + final Resources resources = getContext().getResources(); + + DialpadKeyButton dialpadKey; + TextView numberView; + TextView lettersView; + + final Locale currentLocale = resources.getConfiguration().locale; + final NumberFormat nf; + // We translate dialpad numbers only for "fa" and not any other locale + // ("ar" anybody ?). + if ("fa".equals(currentLocale.getLanguage())) { + nf = DecimalFormat.getInstance(resources.getConfiguration().locale); + } else { + nf = DecimalFormat.getInstance(Locale.ENGLISH); + } + + for (int i = 0; i < mButtonIds.length; i++) { + dialpadKey = (DialpadKeyButton) findViewById(mButtonIds[i]); + numberView = (TextView) dialpadKey.findViewById(R.id.dialpad_key_number); + lettersView = (TextView) dialpadKey.findViewById(R.id.dialpad_key_letters); + + final String numberString; + final CharSequence numberContentDescription; + if (mButtonIds[i] == R.id.pound) { + numberString = resources.getString(R.string.dialpad_pound_number); + numberContentDescription = numberString; + } else if (mButtonIds[i] == R.id.star) { + numberString = resources.getString(R.string.dialpad_star_number); + numberContentDescription = numberString; + } else { + numberString = nf.format(i); + // The content description is used for Talkback key presses. The number is + // separated by a "," to introduce a slight delay. Convert letters into a verbatim + // span so that they are read as letters instead of as one word. + String letters = resources.getString(letterIds[i]); + Spannable spannable = + Spannable.Factory.getInstance().newSpannable(numberString + "," + letters); + spannable.setSpan( + (new TtsSpan.VerbatimBuilder(letters)).build(), + numberString.length() + 1, + numberString.length() + 1 + letters.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + numberContentDescription = spannable; + } + + final RippleDrawable rippleBackground = + (RippleDrawable) getDrawableCompat(getContext(), R.drawable.btn_dialpad_key); + if (mRippleColor != null) { + rippleBackground.setColor(mRippleColor); + } + + numberView.setText(numberString); + numberView.setElegantTextHeight(false); + dialpadKey.setContentDescription(numberContentDescription); + dialpadKey.setBackground(rippleBackground); + + if (lettersView != null) { + lettersView.setText(resources.getString(letterIds[i])); + } + } + + final DialpadKeyButton one = (DialpadKeyButton) findViewById(R.id.one); + one.setLongHoverContentDescription(resources.getText(R.string.description_voicemail_button)); + + final DialpadKeyButton zero = (DialpadKeyButton) findViewById(R.id.zero); + zero.setLongHoverContentDescription(resources.getText(R.string.description_image_button_plus)); + } + + private Drawable getDrawableCompat(Context context, int id) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + return context.getDrawable(id); + } else { + return context.getResources().getDrawable(id); + } + } + + public void setShowVoicemailButton(boolean show) { + View view = findViewById(R.id.dialpad_key_voicemail); + if (view != null) { + view.setVisibility(show ? View.VISIBLE : View.INVISIBLE); + } + } + + /** + * Whether or not the digits above the dialer can be edited. + * + * @param canBeEdited If true, the backspace button will be shown and the digits EditText will be + * configured to allow text manipulation. + */ + public void setCanDigitsBeEdited(boolean canBeEdited) { + View deleteButton = findViewById(R.id.deleteButton); + deleteButton.setVisibility(canBeEdited ? View.VISIBLE : View.INVISIBLE); + View overflowMenuButton = findViewById(R.id.dialpad_overflow); + overflowMenuButton.setVisibility(canBeEdited ? View.VISIBLE : View.GONE); + + EditText digits = (EditText) findViewById(R.id.digits); + digits.setClickable(canBeEdited); + digits.setLongClickable(canBeEdited); + digits.setFocusableInTouchMode(canBeEdited); + digits.setCursorVisible(false); + + mCanDigitsBeEdited = canBeEdited; + } + + public void setCallRateInformation(String countryName, String displayRate) { + if (TextUtils.isEmpty(countryName) && TextUtils.isEmpty(displayRate)) { + mRateContainer.setVisibility(View.GONE); + return; + } + mRateContainer.setVisibility(View.VISIBLE); + mIldCountry.setText(countryName); + mIldRate.setText(displayRate); + } + + public boolean canDigitsBeEdited() { + return mCanDigitsBeEdited; + } + + /** + * Always returns true for onHoverEvent callbacks, to fix problems with accessibility due to the + * dialpad overlaying other fragments. + */ + @Override + public boolean onHoverEvent(MotionEvent event) { + return true; + } + + public void animateShow() { + // This is a hack; without this, the setTranslationY is delayed in being applied, and the + // numbers appear at their original position (0) momentarily before animating. + final AnimatorListenerAdapter showListener = new AnimatorListenerAdapter() {}; + + for (int i = 0; i < mButtonIds.length; i++) { + int delay = (int) (getKeyButtonAnimationDelay(mButtonIds[i]) * DELAY_MULTIPLIER); + int duration = (int) (getKeyButtonAnimationDuration(mButtonIds[i]) * DURATION_MULTIPLIER); + final DialpadKeyButton dialpadKey = (DialpadKeyButton) findViewById(mButtonIds[i]); + + ViewPropertyAnimator animator = dialpadKey.animate(); + if (mIsLandscape) { + // Landscape orientation requires translation along the X axis. + // For RTL locales, ensure we translate negative on the X axis. + dialpadKey.setTranslationX((mIsRtl ? -1 : 1) * mTranslateDistance); + animator.translationX(0); + } else { + // Portrait orientation requires translation along the Y axis. + dialpadKey.setTranslationY(mTranslateDistance); + animator.translationY(0); + } + animator + .setInterpolator(AnimUtils.EASE_OUT_EASE_IN) + .setStartDelay(delay) + .setDuration(duration) + .setListener(showListener) + .start(); + } + } + + public EditText getDigits() { + return mDigits; + } + + public ImageButton getDeleteButton() { + return mDelete; + } + + public View getOverflowMenuButton() { + return mOverflowMenuButton; + } + + /** + * Get the animation delay for the buttons, taking into account whether the dialpad is in + * landscape left-to-right, landscape right-to-left, or portrait. + * + * @param buttonId The button ID. + * @return The animation delay. + */ + private int getKeyButtonAnimationDelay(int buttonId) { + if (mIsLandscape) { + if (mIsRtl) { + if (buttonId == R.id.three) { + return KEY_FRAME_DURATION * 1; + } else if (buttonId == R.id.six) { + return KEY_FRAME_DURATION * 2; + } else if (buttonId == R.id.nine) { + return KEY_FRAME_DURATION * 3; + } else if (buttonId == R.id.pound) { + return KEY_FRAME_DURATION * 4; + } else if (buttonId == R.id.two) { + return KEY_FRAME_DURATION * 5; + } else if (buttonId == R.id.five) { + return KEY_FRAME_DURATION * 6; + } else if (buttonId == R.id.eight) { + return KEY_FRAME_DURATION * 7; + } else if (buttonId == R.id.zero) { + return KEY_FRAME_DURATION * 8; + } else if (buttonId == R.id.one) { + return KEY_FRAME_DURATION * 9; + } else if (buttonId == R.id.four) { + return KEY_FRAME_DURATION * 10; + } else if (buttonId == R.id.seven || buttonId == R.id.star) { + return KEY_FRAME_DURATION * 11; + } + } else { + if (buttonId == R.id.one) { + return KEY_FRAME_DURATION * 1; + } else if (buttonId == R.id.four) { + return KEY_FRAME_DURATION * 2; + } else if (buttonId == R.id.seven) { + return KEY_FRAME_DURATION * 3; + } else if (buttonId == R.id.star) { + return KEY_FRAME_DURATION * 4; + } else if (buttonId == R.id.two) { + return KEY_FRAME_DURATION * 5; + } else if (buttonId == R.id.five) { + return KEY_FRAME_DURATION * 6; + } else if (buttonId == R.id.eight) { + return KEY_FRAME_DURATION * 7; + } else if (buttonId == R.id.zero) { + return KEY_FRAME_DURATION * 8; + } else if (buttonId == R.id.three) { + return KEY_FRAME_DURATION * 9; + } else if (buttonId == R.id.six) { + return KEY_FRAME_DURATION * 10; + } else if (buttonId == R.id.nine || buttonId == R.id.pound) { + return KEY_FRAME_DURATION * 11; + } + } + } else { + if (buttonId == R.id.one) { + return KEY_FRAME_DURATION * 1; + } else if (buttonId == R.id.two) { + return KEY_FRAME_DURATION * 2; + } else if (buttonId == R.id.three) { + return KEY_FRAME_DURATION * 3; + } else if (buttonId == R.id.four) { + return KEY_FRAME_DURATION * 4; + } else if (buttonId == R.id.five) { + return KEY_FRAME_DURATION * 5; + } else if (buttonId == R.id.six) { + return KEY_FRAME_DURATION * 6; + } else if (buttonId == R.id.seven) { + return KEY_FRAME_DURATION * 7; + } else if (buttonId == R.id.eight) { + return KEY_FRAME_DURATION * 8; + } else if (buttonId == R.id.nine) { + return KEY_FRAME_DURATION * 9; + } else if (buttonId == R.id.star) { + return KEY_FRAME_DURATION * 10; + } else if (buttonId == R.id.zero || buttonId == R.id.pound) { + return KEY_FRAME_DURATION * 11; + } + } + + Log.wtf(TAG, "Attempted to get animation delay for invalid key button id."); + return 0; + } + + /** + * Get the button animation duration, taking into account whether the dialpad is in landscape + * left-to-right, landscape right-to-left, or portrait. + * + * @param buttonId The button ID. + * @return The animation duration. + */ + private int getKeyButtonAnimationDuration(int buttonId) { + if (mIsLandscape) { + if (mIsRtl) { + if (buttonId == R.id.one + || buttonId == R.id.four + || buttonId == R.id.seven + || buttonId == R.id.star) { + return KEY_FRAME_DURATION * 8; + } else if (buttonId == R.id.two + || buttonId == R.id.five + || buttonId == R.id.eight + || buttonId == R.id.zero) { + return KEY_FRAME_DURATION * 9; + } else if (buttonId == R.id.three + || buttonId == R.id.six + || buttonId == R.id.nine + || buttonId == R.id.pound) { + return KEY_FRAME_DURATION * 10; + } + } else { + if (buttonId == R.id.one + || buttonId == R.id.four + || buttonId == R.id.seven + || buttonId == R.id.star) { + return KEY_FRAME_DURATION * 10; + } else if (buttonId == R.id.two + || buttonId == R.id.five + || buttonId == R.id.eight + || buttonId == R.id.zero) { + return KEY_FRAME_DURATION * 9; + } else if (buttonId == R.id.three + || buttonId == R.id.six + || buttonId == R.id.nine + || buttonId == R.id.pound) { + return KEY_FRAME_DURATION * 8; + } + } + } else { + if (buttonId == R.id.one + || buttonId == R.id.two + || buttonId == R.id.three + || buttonId == R.id.four + || buttonId == R.id.five + || buttonId == R.id.six) { + return KEY_FRAME_DURATION * 10; + } else if (buttonId == R.id.seven || buttonId == R.id.eight || buttonId == R.id.nine) { + return KEY_FRAME_DURATION * 9; + } else if (buttonId == R.id.star || buttonId == R.id.zero || buttonId == R.id.pound) { + return KEY_FRAME_DURATION * 8; + } + } + + Log.wtf(TAG, "Attempted to get animation duration for invalid key button id."); + return 0; + } +} |