• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
DialpadView(Context context)77     public DialpadView(Context context) {
78         this(context, null);
79     }
80 
DialpadView(Context context, AttributeSet attrs)81     public DialpadView(Context context, AttributeSet attrs) {
82         this(context, attrs, 0);
83     }
84 
DialpadView(Context context, AttributeSet attrs, int defStyle)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
onFinishInflate()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 
setupKeypad()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 
setShowVoicemailButton(boolean show)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      */
setCanDigitsBeEdited(boolean canBeEdited)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 
canDigitsBeEdited()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
onHoverEvent(MotionEvent event)196     public boolean onHoverEvent(MotionEvent event) {
197         return true;
198     }
199 
animateShow()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 
getDigits()230     public EditText getDigits() {
231         return mDigits;
232     }
233 
getDeleteButton()234     public ImageButton getDeleteButton() {
235         return mDelete;
236     }
237 
getOverflowMenuButton()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      */
getKeyButtonAnimationDelay(int buttonId)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      */
getKeyButtonAnimationDuration(int buttonId)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