Home | History | Annotate | Download | only in dialpad
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.phone.common.dialpad;
     18 
     19 import android.animation.AnimatorListenerAdapter;
     20 import android.content.Context;
     21 import android.content.res.ColorStateList;
     22 import android.content.res.Configuration;
     23 import android.content.res.Resources;
     24 import android.content.res.TypedArray;
     25 import android.graphics.drawable.RippleDrawable;
     26 import android.text.TextUtils;
     27 import android.util.AttributeSet;
     28 import android.util.Log;
     29 import android.view.MotionEvent;
     30 import android.view.View;
     31 import android.view.ViewPropertyAnimator;
     32 import android.widget.EditText;
     33 import android.widget.ImageButton;
     34 import android.widget.LinearLayout;
     35 import android.widget.TextView;
     36 
     37 import com.android.phone.common.R;
     38 import com.android.phone.common.animation.AnimUtils;
     39 
     40 import java.util.Locale;
     41 
     42 /**
     43  * View that displays a twelve-key phone dialpad.
     44  */
     45 public class DialpadView extends LinearLayout {
     46     private static final String TAG = DialpadView.class.getSimpleName();
     47 
     48     private static final double DELAY_MULTIPLIER = 0.66;
     49     private static final double DURATION_MULTIPLIER = 0.8;
     50 
     51     /**
     52      * {@code True} if the dialpad is in landscape orientation.
     53      */
     54     private final boolean mIsLandscape;
     55 
     56     /**
     57      * {@code True} if the dialpad is showing in a right-to-left locale.
     58      */
     59     private final boolean mIsRtl;
     60 
     61     private EditText mDigits;
     62     private ImageButton mDelete;
     63     private View mOverflowMenuButton;
     64     private ColorStateList mRippleColor;
     65 
     66     private boolean mCanDigitsBeEdited;
     67 
     68     private final int[] mButtonIds = new int[] {R.id.zero, R.id.one, R.id.two, R.id.three,
     69             R.id.four, R.id.five, R.id.six, R.id.seven, R.id.eight, R.id.nine, R.id.star,
     70             R.id.pound};
     71 
     72     // For animation.
     73     private static final int KEY_FRAME_DURATION = 33;
     74 
     75     private int mTranslateDistance;
     76 
     77     public DialpadView(Context context) {
     78         this(context, null);
     79     }
     80 
     81     public DialpadView(Context context, AttributeSet attrs) {
     82         this(context, attrs, 0);
     83     }
     84 
     85     public DialpadView(Context context, AttributeSet attrs, int defStyle) {
     86         super(context, attrs, defStyle);
     87 
     88         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Dialpad);
     89         mRippleColor = a.getColorStateList(R.styleable.Dialpad_dialpad_key_button_touch_tint);
     90         a.recycle();
     91 
     92         mTranslateDistance = getResources().getDimensionPixelSize(
     93                 R.dimen.dialpad_key_button_translate_y);
     94 
     95         mIsLandscape = getResources().getConfiguration().orientation ==
     96                 Configuration.ORIENTATION_LANDSCAPE;
     97         mIsRtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) ==
     98                 View.LAYOUT_DIRECTION_RTL;
     99     }
    100 
    101     @Override
    102     protected void onFinishInflate() {
    103         setupKeypad();
    104         mDigits = (EditText) findViewById(R.id.digits);
    105         mDelete = (ImageButton) findViewById(R.id.deleteButton);
    106         mOverflowMenuButton = findViewById(R.id.dialpad_overflow);
    107     }
    108 
    109     private void setupKeypad() {
    110         final int[] numberIds = new int[] {R.string.dialpad_0_number, R.string.dialpad_1_number,
    111                 R.string.dialpad_2_number, R.string.dialpad_3_number, R.string.dialpad_4_number,
    112                 R.string.dialpad_5_number, R.string.dialpad_6_number, R.string.dialpad_7_number,
    113                 R.string.dialpad_8_number, R.string.dialpad_9_number, R.string.dialpad_star_number,
    114                 R.string.dialpad_pound_number};
    115 
    116         final int[] letterIds = new int[] {R.string.dialpad_0_letters, R.string.dialpad_1_letters,
    117                 R.string.dialpad_2_letters, R.string.dialpad_3_letters, R.string.dialpad_4_letters,
    118                 R.string.dialpad_5_letters, R.string.dialpad_6_letters, R.string.dialpad_7_letters,
    119                 R.string.dialpad_8_letters, R.string.dialpad_9_letters,
    120                 R.string.dialpad_star_letters, R.string.dialpad_pound_letters};
    121 
    122         final Resources resources = getContext().getResources();
    123 
    124         DialpadKeyButton dialpadKey;
    125         TextView numberView;
    126         TextView lettersView;
    127 
    128         for (int i = 0; i < mButtonIds.length; i++) {
    129             dialpadKey = (DialpadKeyButton) findViewById(mButtonIds[i]);
    130             numberView = (TextView) dialpadKey.findViewById(R.id.dialpad_key_number);
    131             lettersView = (TextView) dialpadKey.findViewById(R.id.dialpad_key_letters);
    132             final String numberString = resources.getString(numberIds[i]);
    133             final RippleDrawable rippleBackground =
    134                     (RippleDrawable) getContext().getDrawable(R.drawable.btn_dialpad_key);
    135             if (mRippleColor != null) {
    136                 rippleBackground.setColor(mRippleColor);
    137             }
    138 
    139             numberView.setText(numberString);
    140             numberView.setElegantTextHeight(false);
    141             dialpadKey.setContentDescription(numberString);
    142             dialpadKey.setBackground(rippleBackground);
    143 
    144             if (lettersView != null) {
    145                 lettersView.setText(resources.getString(letterIds[i]));
    146             }
    147         }
    148 
    149         final DialpadKeyButton one = (DialpadKeyButton) findViewById(R.id.one);
    150         one.setLongHoverContentDescription(
    151                 resources.getText(R.string.description_voicemail_button));
    152 
    153         final DialpadKeyButton zero = (DialpadKeyButton) findViewById(R.id.zero);
    154         zero.setLongHoverContentDescription(
    155                 resources.getText(R.string.description_image_button_plus));
    156 
    157     }
    158 
    159     public void setShowVoicemailButton(boolean show) {
    160         View view = findViewById(R.id.dialpad_key_voicemail);
    161         if (view != null) {
    162             view.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
    163         }
    164     }
    165 
    166     /**
    167      * Whether or not the digits above the dialer can be edited.
    168      *
    169      * @param canBeEdited If true, the backspace button will be shown and the digits EditText
    170      *         will be configured to allow text manipulation.
    171      */
    172     public void setCanDigitsBeEdited(boolean canBeEdited) {
    173         View deleteButton = findViewById(R.id.deleteButton);
    174         deleteButton.setVisibility(canBeEdited ? View.VISIBLE : View.GONE);
    175         View overflowMenuButton = findViewById(R.id.dialpad_overflow);
    176         overflowMenuButton.setVisibility(canBeEdited ? View.VISIBLE : View.GONE);
    177 
    178         EditText digits = (EditText) findViewById(R.id.digits);
    179         digits.setClickable(canBeEdited);
    180         digits.setLongClickable(canBeEdited);
    181         digits.setFocusableInTouchMode(canBeEdited);
    182         digits.setCursorVisible(false);
    183 
    184         mCanDigitsBeEdited = canBeEdited;
    185     }
    186 
    187     public boolean canDigitsBeEdited() {
    188         return mCanDigitsBeEdited;
    189     }
    190 
    191     /**
    192      * Always returns true for onHoverEvent callbacks, to fix problems with accessibility due to
    193      * the dialpad overlaying other fragments.
    194      */
    195     @Override
    196     public boolean onHoverEvent(MotionEvent event) {
    197         return true;
    198     }
    199 
    200     public void animateShow() {
    201         // This is a hack; without this, the setTranslationY is delayed in being applied, and the
    202         // numbers appear at their original position (0) momentarily before animating.
    203         final AnimatorListenerAdapter showListener = new AnimatorListenerAdapter() {};
    204 
    205         for (int i = 0; i < mButtonIds.length; i++) {
    206             int delay = (int)(getKeyButtonAnimationDelay(mButtonIds[i]) * DELAY_MULTIPLIER);
    207             int duration =
    208                     (int)(getKeyButtonAnimationDuration(mButtonIds[i]) * DURATION_MULTIPLIER);
    209             final DialpadKeyButton dialpadKey = (DialpadKeyButton) findViewById(mButtonIds[i]);
    210 
    211             ViewPropertyAnimator animator = dialpadKey.animate();
    212             if (mIsLandscape) {
    213                 // Landscape orientation requires translation along the X axis.
    214                 // For RTL locales, ensure we translate negative on the X axis.
    215                 dialpadKey.setTranslationX((mIsRtl ? -1 : 1) * mTranslateDistance);
    216                 animator.translationX(0);
    217             } else {
    218                 // Portrait orientation requires translation along the Y axis.
    219                 dialpadKey.setTranslationY(mTranslateDistance);
    220                 animator.translationY(0);
    221             }
    222             animator.setInterpolator(AnimUtils.EASE_OUT_EASE_IN)
    223                     .setStartDelay(delay)
    224                     .setDuration(duration)
    225                     .setListener(showListener)
    226                     .start();
    227         }
    228     }
    229 
    230     public EditText getDigits() {
    231         return mDigits;
    232     }
    233 
    234     public ImageButton getDeleteButton() {
    235         return mDelete;
    236     }
    237 
    238     public View getOverflowMenuButton() {
    239         return mOverflowMenuButton;
    240     }
    241 
    242     /**
    243      * Get the animation delay for the buttons, taking into account whether the dialpad is in
    244      * landscape left-to-right, landscape right-to-left, or portrait.
    245      *
    246      * @param buttonId The button ID.
    247      * @return The animation delay.
    248      */
    249     private int getKeyButtonAnimationDelay(int buttonId) {
    250         if (mIsLandscape) {
    251             if (mIsRtl) {
    252                 switch (buttonId) {
    253                     case R.id.three: return KEY_FRAME_DURATION * 1;
    254                     case R.id.six: return KEY_FRAME_DURATION * 2;
    255                     case R.id.nine: return KEY_FRAME_DURATION * 3;
    256                     case R.id.pound: return KEY_FRAME_DURATION * 4;
    257                     case R.id.two: return KEY_FRAME_DURATION * 5;
    258                     case R.id.five: return KEY_FRAME_DURATION * 6;
    259                     case R.id.eight: return KEY_FRAME_DURATION * 7;
    260                     case R.id.zero: return KEY_FRAME_DURATION * 8;
    261                     case R.id.one: return KEY_FRAME_DURATION * 9;
    262                     case R.id.four: return KEY_FRAME_DURATION * 10;
    263                     case R.id.seven:
    264                     case R.id.star:
    265                         return KEY_FRAME_DURATION * 11;
    266                 }
    267             } else {
    268                 switch (buttonId) {
    269                     case R.id.one: return KEY_FRAME_DURATION * 1;
    270                     case R.id.four: return KEY_FRAME_DURATION * 2;
    271                     case R.id.seven: return KEY_FRAME_DURATION * 3;
    272                     case R.id.star: return KEY_FRAME_DURATION * 4;
    273                     case R.id.two: return KEY_FRAME_DURATION * 5;
    274                     case R.id.five: return KEY_FRAME_DURATION * 6;
    275                     case R.id.eight: return KEY_FRAME_DURATION * 7;
    276                     case R.id.zero: return KEY_FRAME_DURATION * 8;
    277                     case R.id.three: return KEY_FRAME_DURATION * 9;
    278                     case R.id.six: return KEY_FRAME_DURATION * 10;
    279                     case R.id.nine:
    280                     case R.id.pound:
    281                         return KEY_FRAME_DURATION * 11;
    282                 }
    283             }
    284         } else {
    285             switch (buttonId) {
    286                 case R.id.one: return KEY_FRAME_DURATION * 1;
    287                 case R.id.two: return KEY_FRAME_DURATION * 2;
    288                 case R.id.three: return KEY_FRAME_DURATION * 3;
    289                 case R.id.four: return KEY_FRAME_DURATION * 4;
    290                 case R.id.five: return KEY_FRAME_DURATION * 5;
    291                 case R.id.six: return KEY_FRAME_DURATION * 6;
    292                 case R.id.seven: return KEY_FRAME_DURATION * 7;
    293                 case R.id.eight: return KEY_FRAME_DURATION * 8;
    294                 case R.id.nine: return KEY_FRAME_DURATION * 9;
    295                 case R.id.star: return KEY_FRAME_DURATION * 10;
    296                 case R.id.zero:
    297                 case R.id.pound:
    298                     return KEY_FRAME_DURATION * 11;
    299             }
    300         }
    301 
    302         Log.wtf(TAG, "Attempted to get animation delay for invalid key button id.");
    303         return 0;
    304     }
    305 
    306     /**
    307      * Get the button animation duration, taking into account whether the dialpad is in landscape
    308      * left-to-right, landscape right-to-left, or portrait.
    309      *
    310      * @param buttonId The button ID.
    311      * @return The animation duration.
    312      */
    313     private int getKeyButtonAnimationDuration(int buttonId) {
    314         if (mIsLandscape) {
    315             if (mIsRtl) {
    316                 switch (buttonId) {
    317                     case R.id.one:
    318                     case R.id.four:
    319                     case R.id.seven:
    320                     case R.id.star:
    321                         return KEY_FRAME_DURATION * 8;
    322                     case R.id.two:
    323                     case R.id.five:
    324                     case R.id.eight:
    325                     case R.id.zero:
    326                         return KEY_FRAME_DURATION * 9;
    327                     case R.id.three:
    328                     case R.id.six:
    329                     case R.id.nine:
    330                     case R.id.pound:
    331                         return KEY_FRAME_DURATION * 10;
    332                 }
    333             } else {
    334                 switch (buttonId) {
    335                     case R.id.one:
    336                     case R.id.four:
    337                     case R.id.seven:
    338                     case R.id.star:
    339                         return KEY_FRAME_DURATION * 10;
    340                     case R.id.two:
    341                     case R.id.five:
    342                     case R.id.eight:
    343                     case R.id.zero:
    344                         return KEY_FRAME_DURATION * 9;
    345                     case R.id.three:
    346                     case R.id.six:
    347                     case R.id.nine:
    348                     case R.id.pound:
    349                         return KEY_FRAME_DURATION * 8;
    350                 }
    351             }
    352         } else {
    353             switch (buttonId) {
    354                 case R.id.one:
    355                 case R.id.two:
    356                 case R.id.three:
    357                 case R.id.four:
    358                 case R.id.five:
    359                 case R.id.six:
    360                     return KEY_FRAME_DURATION * 10;
    361                 case R.id.seven:
    362                 case R.id.eight:
    363                 case R.id.nine:
    364                     return KEY_FRAME_DURATION * 9;
    365                 case R.id.star:
    366                 case R.id.zero:
    367                 case R.id.pound:
    368                     return KEY_FRAME_DURATION * 8;
    369             }
    370         }
    371 
    372         Log.wtf(TAG, "Attempted to get animation duration for invalid key button id.");
    373         return 0;
    374     }
    375 }
    376