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.dialer.dialpadview; 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.Spannable; 27 import android.text.TextUtils; 28 import android.text.style.TtsSpan; 29 import android.util.AttributeSet; 30 import android.util.TypedValue; 31 import android.view.MotionEvent; 32 import android.view.View; 33 import android.view.ViewGroup; 34 import android.view.ViewPropertyAnimator; 35 import android.view.ViewTreeObserver.OnPreDrawListener; 36 import android.view.accessibility.AccessibilityManager; 37 import android.widget.EditText; 38 import android.widget.ImageButton; 39 import android.widget.LinearLayout; 40 import android.widget.TextView; 41 import com.android.dialer.animation.AnimUtils; 42 import com.android.dialer.common.Assert; 43 import com.android.dialer.common.LogUtil; 44 import com.android.dialer.i18n.LocaleUtils; 45 import java.text.DecimalFormat; 46 import java.text.NumberFormat; 47 import java.util.Locale; 48 49 /** View that displays a twelve-key phone dialpad. */ 50 public class DialpadView extends LinearLayout { 51 52 private static final String TAG = DialpadView.class.getSimpleName(); 53 54 // Parameters for animation 55 private static final double DELAY_MULTIPLIER = 0.66; 56 private static final double DURATION_MULTIPLIER = 0.8; 57 private static final int KEY_FRAME_DURATION = 33; 58 59 // Resource IDs for buttons (0-9, *, and #) 60 private static final int[] BUTTON_IDS = 61 new int[] { 62 R.id.zero, 63 R.id.one, 64 R.id.two, 65 R.id.three, 66 R.id.four, 67 R.id.five, 68 R.id.six, 69 R.id.seven, 70 R.id.eight, 71 R.id.nine, 72 R.id.star, 73 R.id.pound 74 }; 75 76 private final AttributeSet attributeSet; 77 private final ColorStateList rippleColor; 78 private final OnPreDrawListenerForKeyLayoutAdjust onPreDrawListenerForKeyLayoutAdjust; 79 private final String[] primaryLettersMapping; 80 private final String[] secondaryLettersMapping; 81 private final boolean isRtl; // whether the dialpad is shown in a right-to-left locale 82 private final int translateDistance; 83 84 private EditText digits; 85 private TextView digitsHint; 86 private ImageButton delete; 87 private View overflowMenuButton; 88 private ViewGroup rateContainer; 89 private TextView ildCountry; 90 private TextView ildRate; 91 private boolean isLandscapeMode; 92 DialpadView(Context context)93 public DialpadView(Context context) { 94 this(context, null); 95 } 96 DialpadView(Context context, AttributeSet attrs)97 public DialpadView(Context context, AttributeSet attrs) { 98 this(context, attrs, 0); 99 } 100 DialpadView(Context context, AttributeSet attrs, int defStyle)101 public DialpadView(Context context, AttributeSet attrs, int defStyle) { 102 super(context, attrs, defStyle); 103 attributeSet = attrs; 104 105 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Dialpad); 106 rippleColor = a.getColorStateList(R.styleable.Dialpad_dialpad_key_button_touch_tint); 107 a.recycle(); 108 109 translateDistance = 110 getResources().getDimensionPixelSize(R.dimen.dialpad_key_button_translate_y); 111 isRtl = 112 TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL; 113 114 primaryLettersMapping = DialpadCharMappings.getDefaultKeyToCharsMap(); 115 secondaryLettersMapping = DialpadCharMappings.getKeyToCharsMap(context); 116 117 onPreDrawListenerForKeyLayoutAdjust = new OnPreDrawListenerForKeyLayoutAdjust(); 118 } 119 120 @Override onDetachedFromWindow()121 protected void onDetachedFromWindow() { 122 super.onDetachedFromWindow(); 123 getViewTreeObserver().removeOnPreDrawListener(onPreDrawListenerForKeyLayoutAdjust); 124 } 125 126 @Override onFinishInflate()127 protected void onFinishInflate() { 128 super.onFinishInflate(); 129 130 // The orientation obtained at this point should be used as the only truth for DialpadView as we 131 // observed inconsistency between configurations obtained here and in 132 // OnPreDrawListenerForKeyLayoutAdjust under rare circumstances. 133 isLandscapeMode = 134 (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE); 135 136 setupKeypad(); 137 digits = (EditText) findViewById(R.id.digits); 138 digitsHint = findViewById(R.id.digits_hint); 139 delete = (ImageButton) findViewById(R.id.deleteButton); 140 overflowMenuButton = findViewById(R.id.dialpad_overflow); 141 rateContainer = (ViewGroup) findViewById(R.id.rate_container); 142 ildCountry = (TextView) rateContainer.findViewById(R.id.ild_country); 143 ildRate = (TextView) rateContainer.findViewById(R.id.ild_rate); 144 145 AccessibilityManager accessibilityManager = 146 (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); 147 if (accessibilityManager.isEnabled()) { 148 // The text view must be selected to send accessibility events. 149 digits.setSelected(true); 150 } 151 152 // As OnPreDrawListenerForKeyLayoutAdjust makes changes to LayoutParams, it is added here to 153 // ensure it can only be triggered after the layout is inflated. 154 getViewTreeObserver().removeOnPreDrawListener(onPreDrawListenerForKeyLayoutAdjust); 155 getViewTreeObserver().addOnPreDrawListener(onPreDrawListenerForKeyLayoutAdjust); 156 } 157 setupKeypad()158 private void setupKeypad() { 159 final Resources resources = getContext().getResources(); 160 final NumberFormat numberFormat = getNumberFormat(); 161 162 for (int i = 0; i < BUTTON_IDS.length; i++) { 163 DialpadKeyButton dialpadKey = (DialpadKeyButton) findViewById(BUTTON_IDS[i]); 164 TextView numberView = (TextView) dialpadKey.findViewById(R.id.dialpad_key_number); 165 166 final String numberString; 167 final CharSequence numberContentDescription; 168 if (BUTTON_IDS[i] == R.id.pound) { 169 numberString = resources.getString(R.string.dialpad_pound_number); 170 numberContentDescription = numberString; 171 } else if (BUTTON_IDS[i] == R.id.star) { 172 numberString = resources.getString(R.string.dialpad_star_number); 173 numberContentDescription = numberString; 174 } else if (BUTTON_IDS[i] == R.id.zero) { 175 numberString = numberFormat.format(i); 176 numberContentDescription = numberString; 177 } else { 178 numberString = numberFormat.format(i); 179 // The content description is used for Talkback key presses. The number is 180 // separated by a "," to introduce a slight delay. Convert letters into a verbatim 181 // span so that they are read as letters instead of as one word. 182 String letters = primaryLettersMapping[i]; 183 Spannable spannable = 184 Spannable.Factory.getInstance().newSpannable(numberString + "," + letters); 185 spannable.setSpan( 186 (new TtsSpan.VerbatimBuilder(letters)).build(), 187 numberString.length() + 1, 188 numberString.length() + 1 + letters.length(), 189 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 190 numberContentDescription = spannable; 191 } 192 193 final RippleDrawable rippleBackground = 194 (RippleDrawable) getContext().getDrawable(R.drawable.btn_dialpad_key); 195 if (rippleColor != null) { 196 rippleBackground.setColor(rippleColor); 197 } 198 199 numberView.setText(numberString); 200 numberView.setElegantTextHeight(false); 201 dialpadKey.setContentDescription(numberContentDescription); 202 dialpadKey.setBackground(rippleBackground); 203 204 TextView primaryLettersView = (TextView) dialpadKey.findViewById(R.id.dialpad_key_letters); 205 TextView secondaryLettersView = 206 (TextView) dialpadKey.findViewById(R.id.dialpad_key_secondary_letters); 207 if (primaryLettersView != null) { 208 primaryLettersView.setText(primaryLettersMapping[i]); 209 } 210 if (primaryLettersView != null && secondaryLettersView != null) { 211 if (secondaryLettersMapping == null) { 212 secondaryLettersView.setVisibility(View.GONE); 213 } else { 214 secondaryLettersView.setVisibility(View.VISIBLE); 215 secondaryLettersView.setText(secondaryLettersMapping[i]); 216 217 // Adjust the font size of the letters if a secondary alphabet is available. 218 TypedArray a = 219 getContext() 220 .getTheme() 221 .obtainStyledAttributes(attributeSet, R.styleable.Dialpad, 0, 0); 222 int textSize = 223 a.getDimensionPixelSize( 224 R.styleable.Dialpad_dialpad_key_letters_size_for_dual_alphabets, 0); 225 a.recycle(); 226 primaryLettersView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); 227 secondaryLettersView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); 228 } 229 } 230 } 231 232 final DialpadKeyButton one = (DialpadKeyButton) findViewById(R.id.one); 233 one.setLongHoverContentDescription(resources.getText(R.string.description_voicemail_button)); 234 235 final DialpadKeyButton zero = (DialpadKeyButton) findViewById(R.id.zero); 236 zero.setLongHoverContentDescription(resources.getText(R.string.description_image_button_plus)); 237 } 238 getNumberFormat()239 private NumberFormat getNumberFormat() { 240 Locale locale = LocaleUtils.getLocale(getContext()); 241 242 // Return the Persian number format if the current language is Persian. 243 return "fas".equals(locale.getISO3Language()) 244 ? DecimalFormat.getInstance(locale) 245 : DecimalFormat.getInstance(Locale.ENGLISH); 246 } 247 248 /** 249 * Configure whether or not the digits above the dialpad can be edited. 250 * 251 * <p>If we allow editing digits, the backspace button will be shown. 252 */ setCanDigitsBeEdited(boolean canBeEdited)253 public void setCanDigitsBeEdited(boolean canBeEdited) { 254 View deleteButton = findViewById(R.id.deleteButton); 255 deleteButton.setVisibility(canBeEdited ? View.VISIBLE : View.INVISIBLE); 256 View overflowMenuButton = findViewById(R.id.dialpad_overflow); 257 overflowMenuButton.setVisibility(canBeEdited ? View.VISIBLE : View.GONE); 258 259 EditText digits = (EditText) findViewById(R.id.digits); 260 digits.setClickable(canBeEdited); 261 digits.setLongClickable(canBeEdited); 262 digits.setFocusableInTouchMode(canBeEdited); 263 digits.setCursorVisible(false); 264 } 265 setCallRateInformation(String countryName, String displayRate)266 public void setCallRateInformation(String countryName, String displayRate) { 267 if (TextUtils.isEmpty(countryName) && TextUtils.isEmpty(displayRate)) { 268 rateContainer.setVisibility(View.GONE); 269 return; 270 } 271 rateContainer.setVisibility(View.VISIBLE); 272 ildCountry.setText(countryName); 273 ildRate.setText(displayRate); 274 } 275 276 /** 277 * Always returns true for onHoverEvent callbacks, to fix problems with accessibility due to the 278 * dialpad overlaying other fragments. 279 */ 280 @Override onHoverEvent(MotionEvent event)281 public boolean onHoverEvent(MotionEvent event) { 282 return true; 283 } 284 animateShow()285 public void animateShow() { 286 // This is a hack; without this, the setTranslationY is delayed in being applied, and the 287 // numbers appear at their original position (0) momentarily before animating. 288 final AnimatorListenerAdapter showListener = new AnimatorListenerAdapter() {}; 289 290 for (int i = 0; i < BUTTON_IDS.length; i++) { 291 int delay = (int) (getKeyButtonAnimationDelay(BUTTON_IDS[i]) * DELAY_MULTIPLIER); 292 int duration = (int) (getKeyButtonAnimationDuration(BUTTON_IDS[i]) * DURATION_MULTIPLIER); 293 final DialpadKeyButton dialpadKey = (DialpadKeyButton) findViewById(BUTTON_IDS[i]); 294 295 ViewPropertyAnimator animator = dialpadKey.animate(); 296 if (isLandscapeMode) { 297 // Landscape orientation requires translation along the X axis. 298 // For RTL locales, ensure we translate negative on the X axis. 299 dialpadKey.setTranslationX((isRtl ? -1 : 1) * translateDistance); 300 animator.translationX(0); 301 } else { 302 // Portrait orientation requires translation along the Y axis. 303 dialpadKey.setTranslationY(translateDistance); 304 animator.translationY(0); 305 } 306 animator 307 .setInterpolator(AnimUtils.EASE_OUT_EASE_IN) 308 .setStartDelay(delay) 309 .setDuration(duration) 310 .setListener(showListener) 311 .start(); 312 } 313 } 314 getDigits()315 public EditText getDigits() { 316 return digits; 317 } 318 getDigitsHint()319 public TextView getDigitsHint() { 320 return digitsHint; 321 } 322 getDeleteButton()323 public ImageButton getDeleteButton() { 324 return delete; 325 } 326 getOverflowMenuButton()327 public View getOverflowMenuButton() { 328 return overflowMenuButton; 329 } 330 331 /** 332 * Get the animation delay for the buttons, taking into account whether the dialpad is in 333 * landscape left-to-right, landscape right-to-left, or portrait. 334 * 335 * @param buttonId The button ID. 336 * @return The animation delay. 337 */ getKeyButtonAnimationDelay(int buttonId)338 private int getKeyButtonAnimationDelay(int buttonId) { 339 if (isLandscapeMode) { 340 if (isRtl) { 341 if (buttonId == R.id.three) { 342 return KEY_FRAME_DURATION * 1; 343 } else if (buttonId == R.id.six) { 344 return KEY_FRAME_DURATION * 2; 345 } else if (buttonId == R.id.nine) { 346 return KEY_FRAME_DURATION * 3; 347 } else if (buttonId == R.id.pound) { 348 return KEY_FRAME_DURATION * 4; 349 } else if (buttonId == R.id.two) { 350 return KEY_FRAME_DURATION * 5; 351 } else if (buttonId == R.id.five) { 352 return KEY_FRAME_DURATION * 6; 353 } else if (buttonId == R.id.eight) { 354 return KEY_FRAME_DURATION * 7; 355 } else if (buttonId == R.id.zero) { 356 return KEY_FRAME_DURATION * 8; 357 } else if (buttonId == R.id.one) { 358 return KEY_FRAME_DURATION * 9; 359 } else if (buttonId == R.id.four) { 360 return KEY_FRAME_DURATION * 10; 361 } else if (buttonId == R.id.seven || buttonId == R.id.star) { 362 return KEY_FRAME_DURATION * 11; 363 } 364 } else { 365 if (buttonId == R.id.one) { 366 return KEY_FRAME_DURATION * 1; 367 } else if (buttonId == R.id.four) { 368 return KEY_FRAME_DURATION * 2; 369 } else if (buttonId == R.id.seven) { 370 return KEY_FRAME_DURATION * 3; 371 } else if (buttonId == R.id.star) { 372 return KEY_FRAME_DURATION * 4; 373 } else if (buttonId == R.id.two) { 374 return KEY_FRAME_DURATION * 5; 375 } else if (buttonId == R.id.five) { 376 return KEY_FRAME_DURATION * 6; 377 } else if (buttonId == R.id.eight) { 378 return KEY_FRAME_DURATION * 7; 379 } else if (buttonId == R.id.zero) { 380 return KEY_FRAME_DURATION * 8; 381 } else if (buttonId == R.id.three) { 382 return KEY_FRAME_DURATION * 9; 383 } else if (buttonId == R.id.six) { 384 return KEY_FRAME_DURATION * 10; 385 } else if (buttonId == R.id.nine || buttonId == R.id.pound) { 386 return KEY_FRAME_DURATION * 11; 387 } 388 } 389 } else { 390 if (buttonId == R.id.one) { 391 return KEY_FRAME_DURATION * 1; 392 } else if (buttonId == R.id.two) { 393 return KEY_FRAME_DURATION * 2; 394 } else if (buttonId == R.id.three) { 395 return KEY_FRAME_DURATION * 3; 396 } else if (buttonId == R.id.four) { 397 return KEY_FRAME_DURATION * 4; 398 } else if (buttonId == R.id.five) { 399 return KEY_FRAME_DURATION * 5; 400 } else if (buttonId == R.id.six) { 401 return KEY_FRAME_DURATION * 6; 402 } else if (buttonId == R.id.seven) { 403 return KEY_FRAME_DURATION * 7; 404 } else if (buttonId == R.id.eight) { 405 return KEY_FRAME_DURATION * 8; 406 } else if (buttonId == R.id.nine) { 407 return KEY_FRAME_DURATION * 9; 408 } else if (buttonId == R.id.star) { 409 return KEY_FRAME_DURATION * 10; 410 } else if (buttonId == R.id.zero || buttonId == R.id.pound) { 411 return KEY_FRAME_DURATION * 11; 412 } 413 } 414 415 LogUtil.e(TAG, "Attempted to get animation delay for invalid key button id."); 416 return 0; 417 } 418 419 /** 420 * Get the button animation duration, taking into account whether the dialpad is in landscape 421 * left-to-right, landscape right-to-left, or portrait. 422 * 423 * @param buttonId The button ID. 424 * @return The animation duration. 425 */ getKeyButtonAnimationDuration(int buttonId)426 private int getKeyButtonAnimationDuration(int buttonId) { 427 if (isLandscapeMode) { 428 if (isRtl) { 429 if (buttonId == R.id.one 430 || buttonId == R.id.four 431 || buttonId == R.id.seven 432 || buttonId == R.id.star) { 433 return KEY_FRAME_DURATION * 8; 434 } else if (buttonId == R.id.two 435 || buttonId == R.id.five 436 || buttonId == R.id.eight 437 || buttonId == R.id.zero) { 438 return KEY_FRAME_DURATION * 9; 439 } else if (buttonId == R.id.three 440 || buttonId == R.id.six 441 || buttonId == R.id.nine 442 || buttonId == R.id.pound) { 443 return KEY_FRAME_DURATION * 10; 444 } 445 } else { 446 if (buttonId == R.id.one 447 || buttonId == R.id.four 448 || buttonId == R.id.seven 449 || buttonId == R.id.star) { 450 return KEY_FRAME_DURATION * 10; 451 } else if (buttonId == R.id.two 452 || buttonId == R.id.five 453 || buttonId == R.id.eight 454 || buttonId == R.id.zero) { 455 return KEY_FRAME_DURATION * 9; 456 } else if (buttonId == R.id.three 457 || buttonId == R.id.six 458 || buttonId == R.id.nine 459 || buttonId == R.id.pound) { 460 return KEY_FRAME_DURATION * 8; 461 } 462 } 463 } else { 464 if (buttonId == R.id.one 465 || buttonId == R.id.two 466 || buttonId == R.id.three 467 || buttonId == R.id.four 468 || buttonId == R.id.five 469 || buttonId == R.id.six) { 470 return KEY_FRAME_DURATION * 10; 471 } else if (buttonId == R.id.seven || buttonId == R.id.eight || buttonId == R.id.nine) { 472 return KEY_FRAME_DURATION * 9; 473 } else if (buttonId == R.id.star || buttonId == R.id.zero || buttonId == R.id.pound) { 474 return KEY_FRAME_DURATION * 8; 475 } 476 } 477 478 LogUtil.e(TAG, "Attempted to get animation duration for invalid key button id."); 479 return 0; 480 } 481 482 /** 483 * An {@link OnPreDrawListener} that adjusts the height/width of each key layout so that they can 484 * be properly aligned. 485 * 486 * <p>When the device is in portrait mode, the layout height for key "1" can be different from 487 * those of other <b>digit</b> keys due to the voicemail icon. Adjustments are needed to ensure 488 * the layouts for all <b>digit</b> keys are of the same height. Key "*" and key "#" are excluded 489 * because their styles are different from other keys'. 490 * 491 * <p>When the device is in landscape mode, keys can have different layout widths due to the 492 * icon/characters associated with them. Adjustments are needed to ensure the layouts for all keys 493 * are of the same width. 494 * 495 * <p>Note that adjustments can only be made after the layouts are measured, which is why the 496 * logic lives in an {@link OnPreDrawListener} that is invoked when the view tree is about to be 497 * drawn. 498 */ 499 private class OnPreDrawListenerForKeyLayoutAdjust implements OnPreDrawListener { 500 501 /** 502 * This method is invoked when the view tree is about to be drawn. At this point, all views in 503 * the tree have been measured and given a frame. 504 * 505 * <p>If the keys have been adjusted, we instruct the current drawing pass to proceed by 506 * returning true. Otherwise, adjustments will be made and the current drawing pass will be 507 * cancelled by returning false. 508 * 509 * <p>It is imperative to schedule another layout pass of the view tree after adjustments are 510 * made so that {@link #onPreDraw()} can be invoked again to check the layouts and proceed with 511 * the drawing pass. 512 */ 513 @Override onPreDraw()514 public boolean onPreDraw() { 515 if (!shouldAdjustKeySizes()) { 516 // Return true to proceed with the current drawing pass. 517 // Note that we must NOT remove this listener here. The fact that we don't need to adjust 518 // keys at the moment doesn't mean they won't need adjustments in the future. For example, 519 // when DialpadFragment is hidden, all keys are of the same size (0) and nothing needs to be 520 // done. Removing the listener will cost us the ability to adjust them when they reappear. 521 // It is only safe to remove the listener after adjusting keys for the first time. See the 522 // comment below for more details. 523 return true; 524 } 525 526 adjustKeySizes(); 527 528 // After all keys are adjusted for the first time, no more adjustments will be needed during 529 // the rest of DialpadView's lifecycle. It is therefore safe to remove this listener. 530 // Another important reason for removing the listener is that it can be triggered AFTER a 531 // device orientation change but BEFORE DialpadView's onDetachedFromWindow() and 532 // onFinishInflate() are called, i.e., the listener will attempt to adjust the layout before 533 // it is inflated, which results in a crash. 534 getViewTreeObserver().removeOnPreDrawListener(this); 535 536 return false; // Return false to cancel the current drawing pass. 537 } 538 shouldAdjustKeySizes()539 private boolean shouldAdjustKeySizes() { 540 return isLandscapeMode ? shouldAdjustKeyWidths() : shouldAdjustDigitKeyHeights(); 541 } 542 543 /** 544 * Return true if not all key layouts have the same width. This method must be called when the 545 * device is in landscape mode. 546 */ shouldAdjustKeyWidths()547 private boolean shouldAdjustKeyWidths() { 548 Assert.checkState(isLandscapeMode); 549 550 DialpadKeyButton dialpadKeyButton = (DialpadKeyButton) findViewById(BUTTON_IDS[0]); 551 LinearLayout keyLayout = 552 (LinearLayout) dialpadKeyButton.findViewById(R.id.dialpad_key_layout); 553 final int width = keyLayout.getWidth(); 554 555 for (int i = 1; i < BUTTON_IDS.length; i++) { 556 dialpadKeyButton = (DialpadKeyButton) findViewById(BUTTON_IDS[i]); 557 keyLayout = (LinearLayout) dialpadKeyButton.findViewById(R.id.dialpad_key_layout); 558 if (width != keyLayout.getWidth()) { 559 return true; 560 } 561 } 562 563 return false; 564 } 565 566 /** 567 * Return true if not all <b>digit</b> key layouts have the same height. This method must be 568 * called when the device is in portrait mode. 569 */ shouldAdjustDigitKeyHeights()570 private boolean shouldAdjustDigitKeyHeights() { 571 Assert.checkState(!isLandscapeMode); 572 573 DialpadKeyButton dialpadKey = (DialpadKeyButton) findViewById(BUTTON_IDS[0]); 574 LinearLayout keyLayout = (LinearLayout) dialpadKey.findViewById(R.id.dialpad_key_layout); 575 final int height = keyLayout.getHeight(); 576 577 // BUTTON_IDS[i] is the resource ID for button i when 0 <= i && i <= 9. 578 // For example, BUTTON_IDS[3] is the resource ID for button "3" on the dialpad. 579 for (int i = 1; i <= 9; i++) { 580 dialpadKey = (DialpadKeyButton) findViewById(BUTTON_IDS[i]); 581 keyLayout = (LinearLayout) dialpadKey.findViewById(R.id.dialpad_key_layout); 582 if (height != keyLayout.getHeight()) { 583 return true; 584 } 585 } 586 587 return false; 588 } 589 adjustKeySizes()590 private void adjustKeySizes() { 591 if (isLandscapeMode) { 592 adjustKeyWidths(); 593 } else { 594 adjustDigitKeyHeights(); 595 } 596 } 597 598 /** 599 * Make the heights of all <b>digit</b> keys the same. 600 * 601 * <p>When the device is in portrait mode, we first find the maximum height among digit key 602 * layouts. Then for each key, we adjust the height of the layout containing letters/the 603 * voicemail icon to ensure the height of each digit key is the same. 604 * 605 * <p>A layout pass will be scheduled in this method by {@link 606 * LinearLayout#setLayoutParams(ViewGroup.LayoutParams)}. 607 */ adjustDigitKeyHeights()608 private void adjustDigitKeyHeights() { 609 Assert.checkState(!isLandscapeMode); 610 611 int maxHeight = 0; 612 613 // BUTTON_IDS[i] is the resource ID for button i when 0 <= i && i <= 9. 614 // For example, BUTTON_IDS[3] is the resource ID for button "3" on the dialpad. 615 for (int i = 0; i <= 9; i++) { 616 DialpadKeyButton dialpadKey = (DialpadKeyButton) findViewById(BUTTON_IDS[i]); 617 LinearLayout keyLayout = (LinearLayout) dialpadKey.findViewById(R.id.dialpad_key_layout); 618 maxHeight = Math.max(maxHeight, keyLayout.getHeight()); 619 } 620 621 for (int i = 0; i <= 9; i++) { 622 DialpadKeyButton dialpadKey = (DialpadKeyButton) findViewById(BUTTON_IDS[i]); 623 LinearLayout keyLayout = (LinearLayout) dialpadKey.findViewById(R.id.dialpad_key_layout); 624 625 DialpadTextView numberView = 626 (DialpadTextView) keyLayout.findViewById(R.id.dialpad_key_number); 627 MarginLayoutParams numberViewLayoutParams = 628 (MarginLayoutParams) numberView.getLayoutParams(); 629 630 LinearLayout iconOrLettersLayout = 631 (LinearLayout) keyLayout.findViewById(R.id.dialpad_key_icon_or_letters_layout); 632 iconOrLettersLayout.setLayoutParams( 633 new LayoutParams( 634 LayoutParams.WRAP_CONTENT /* width */, 635 maxHeight 636 - numberView.getHeight() 637 - numberViewLayoutParams.topMargin 638 - numberViewLayoutParams.bottomMargin /* height */)); 639 } 640 } 641 642 /** 643 * Make the widths of all keys the same. 644 * 645 * <p>When the device is in landscape mode, we first find the maximum width among key layouts. 646 * Then we adjust the width of each layout's horizontal placeholder so that each key has the 647 * same width. 648 * 649 * <p>A layout pass will be scheduled in this method by {@link 650 * View#setLayoutParams(ViewGroup.LayoutParams)}. 651 */ adjustKeyWidths()652 private void adjustKeyWidths() { 653 Assert.checkState(isLandscapeMode); 654 655 int maxWidth = 0; 656 for (int buttonId : BUTTON_IDS) { 657 DialpadKeyButton dialpadKey = (DialpadKeyButton) findViewById(buttonId); 658 LinearLayout keyLayout = (LinearLayout) dialpadKey.findViewById(R.id.dialpad_key_layout); 659 maxWidth = Math.max(maxWidth, keyLayout.getWidth()); 660 } 661 662 for (int buttonId : BUTTON_IDS) { 663 DialpadKeyButton dialpadKey = (DialpadKeyButton) findViewById(buttonId); 664 LinearLayout keyLayout = (LinearLayout) dialpadKey.findViewById(R.id.dialpad_key_layout); 665 View horizontalPlaceholder = 666 keyLayout.findViewById(R.id.dialpad_key_horizontal_placeholder); 667 horizontalPlaceholder.setLayoutParams( 668 new LayoutParams( 669 maxWidth - keyLayout.getWidth() /* width */, 670 LayoutParams.MATCH_PARENT /* height */)); 671 } 672 } 673 } 674 } 675