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