| /* |
| * 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.phone.common.dialpad; |
| |
| 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.phone.common.R; |
| import com.android.phone.common.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; |
| |
| /** |
| * {@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 EditText mDigits; |
| private ImageButton mDelete; |
| private View mOverflowMenuButton; |
| private ColorStateList mRippleColor; |
| |
| private ViewGroup mRateContainer; |
| private TextView mIldCountry; |
| private TextView mIldRate; |
| |
| private boolean mCanDigitsBeEdited; |
| |
| 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}; |
| |
| // For animation. |
| private static final int KEY_FRAME_DURATION = 33; |
| |
| 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.GONE); |
| 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; |
| } |
| } |