/* * 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; } }