Java程序  |  675行  |  26.18 KB

/*
 * 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.dialer.dialpadview;

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.RippleDrawable;
import android.text.Spannable;
import android.text.TextUtils;
import android.text.style.TtsSpan;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.view.ViewTreeObserver.OnPreDrawListener;
import android.view.accessibility.AccessibilityManager;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.dialer.animation.AnimUtils;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
import com.android.dialer.i18n.LocaleUtils;
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();

  // Parameters for animation
  private static final double DELAY_MULTIPLIER = 0.66;
  private static final double DURATION_MULTIPLIER = 0.8;
  private static final int KEY_FRAME_DURATION = 33;

  // Resource IDs for buttons (0-9, *, and #)
  private static final int[] BUTTON_IDS =
      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
      };

  private final AttributeSet attributeSet;
  private final ColorStateList rippleColor;
  private final OnPreDrawListenerForKeyLayoutAdjust onPreDrawListenerForKeyLayoutAdjust;
  private final String[] primaryLettersMapping;
  private final String[] secondaryLettersMapping;
  private final boolean isRtl; // whether the dialpad is shown in a right-to-left locale
  private final int translateDistance;

  private EditText digits;
  private TextView digitsHint;
  private ImageButton delete;
  private View overflowMenuButton;
  private ViewGroup rateContainer;
  private TextView ildCountry;
  private TextView ildRate;
  private boolean isLandscapeMode;

  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);
    attributeSet = attrs;

    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Dialpad);
    rippleColor = a.getColorStateList(R.styleable.Dialpad_dialpad_key_button_touch_tint);
    a.recycle();

    translateDistance =
        getResources().getDimensionPixelSize(R.dimen.dialpad_key_button_translate_y);
    isRtl =
        TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL;

    primaryLettersMapping = DialpadCharMappings.getDefaultKeyToCharsMap();
    secondaryLettersMapping = DialpadCharMappings.getKeyToCharsMap(context);

    onPreDrawListenerForKeyLayoutAdjust = new OnPreDrawListenerForKeyLayoutAdjust();
  }

  @Override
  protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    getViewTreeObserver().removeOnPreDrawListener(onPreDrawListenerForKeyLayoutAdjust);
  }

  @Override
  protected void onFinishInflate() {
    super.onFinishInflate();

    // The orientation obtained at this point should be used as the only truth for DialpadView as we
    // observed inconsistency between configurations obtained here and in
    // OnPreDrawListenerForKeyLayoutAdjust under rare circumstances.
    isLandscapeMode =
        (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE);

    setupKeypad();
    digits = (EditText) findViewById(R.id.digits);
    digitsHint = findViewById(R.id.digits_hint);
    delete = (ImageButton) findViewById(R.id.deleteButton);
    overflowMenuButton = findViewById(R.id.dialpad_overflow);
    rateContainer = (ViewGroup) findViewById(R.id.rate_container);
    ildCountry = (TextView) rateContainer.findViewById(R.id.ild_country);
    ildRate = (TextView) rateContainer.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.
      digits.setSelected(true);
    }

    // As OnPreDrawListenerForKeyLayoutAdjust makes changes to LayoutParams, it is added here to
    // ensure it can only be triggered after the layout is inflated.
    getViewTreeObserver().removeOnPreDrawListener(onPreDrawListenerForKeyLayoutAdjust);
    getViewTreeObserver().addOnPreDrawListener(onPreDrawListenerForKeyLayoutAdjust);
  }

  private void setupKeypad() {
    final Resources resources = getContext().getResources();
    final NumberFormat numberFormat = getNumberFormat();

    for (int i = 0; i < BUTTON_IDS.length; i++) {
      DialpadKeyButton dialpadKey = (DialpadKeyButton) findViewById(BUTTON_IDS[i]);
      TextView numberView = (TextView) dialpadKey.findViewById(R.id.dialpad_key_number);

      final String numberString;
      final CharSequence numberContentDescription;
      if (BUTTON_IDS[i] == R.id.pound) {
        numberString = resources.getString(R.string.dialpad_pound_number);
        numberContentDescription = numberString;
      } else if (BUTTON_IDS[i] == R.id.star) {
        numberString = resources.getString(R.string.dialpad_star_number);
        numberContentDescription = numberString;
      } else if (BUTTON_IDS[i] == R.id.zero) {
        numberString = numberFormat.format(i);
        numberContentDescription = numberString;
      } else {
        numberString = numberFormat.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 = primaryLettersMapping[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) getContext().getDrawable(R.drawable.btn_dialpad_key);
      if (rippleColor != null) {
        rippleBackground.setColor(rippleColor);
      }

      numberView.setText(numberString);
      numberView.setElegantTextHeight(false);
      dialpadKey.setContentDescription(numberContentDescription);
      dialpadKey.setBackground(rippleBackground);

      TextView primaryLettersView = (TextView) dialpadKey.findViewById(R.id.dialpad_key_letters);
      TextView secondaryLettersView =
          (TextView) dialpadKey.findViewById(R.id.dialpad_key_secondary_letters);
      if (primaryLettersView != null) {
        primaryLettersView.setText(primaryLettersMapping[i]);
      }
      if (primaryLettersView != null && secondaryLettersView != null) {
        if (secondaryLettersMapping == null) {
          secondaryLettersView.setVisibility(View.GONE);
        } else {
          secondaryLettersView.setVisibility(View.VISIBLE);
          secondaryLettersView.setText(secondaryLettersMapping[i]);

          // Adjust the font size of the letters if a secondary alphabet is available.
          TypedArray a =
              getContext()
                  .getTheme()
                  .obtainStyledAttributes(attributeSet, R.styleable.Dialpad, 0, 0);
          int textSize =
              a.getDimensionPixelSize(
                  R.styleable.Dialpad_dialpad_key_letters_size_for_dual_alphabets, 0);
          a.recycle();
          primaryLettersView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
          secondaryLettersView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
        }
      }
    }

    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 NumberFormat getNumberFormat() {
    Locale locale = LocaleUtils.getLocale(getContext());

    // Return the Persian number format if the current language is Persian.
    return "fas".equals(locale.getISO3Language())
        ? DecimalFormat.getInstance(locale)
        : DecimalFormat.getInstance(Locale.ENGLISH);
  }

  /**
   * Configure whether or not the digits above the dialpad can be edited.
   *
   * <p>If we allow editing digits, the backspace button will be shown.
   */
  public void setCanDigitsBeEdited(boolean canBeEdited) {
    View deleteButton = findViewById(R.id.deleteButton);
    deleteButton.setVisibility(canBeEdited ? View.VISIBLE : View.INVISIBLE);
    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);
  }

  public void setCallRateInformation(String countryName, String displayRate) {
    if (TextUtils.isEmpty(countryName) && TextUtils.isEmpty(displayRate)) {
      rateContainer.setVisibility(View.GONE);
      return;
    }
    rateContainer.setVisibility(View.VISIBLE);
    ildCountry.setText(countryName);
    ildRate.setText(displayRate);
  }

  /**
   * 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 < BUTTON_IDS.length; i++) {
      int delay = (int) (getKeyButtonAnimationDelay(BUTTON_IDS[i]) * DELAY_MULTIPLIER);
      int duration = (int) (getKeyButtonAnimationDuration(BUTTON_IDS[i]) * DURATION_MULTIPLIER);
      final DialpadKeyButton dialpadKey = (DialpadKeyButton) findViewById(BUTTON_IDS[i]);

      ViewPropertyAnimator animator = dialpadKey.animate();
      if (isLandscapeMode) {
        // Landscape orientation requires translation along the X axis.
        // For RTL locales, ensure we translate negative on the X axis.
        dialpadKey.setTranslationX((isRtl ? -1 : 1) * translateDistance);
        animator.translationX(0);
      } else {
        // Portrait orientation requires translation along the Y axis.
        dialpadKey.setTranslationY(translateDistance);
        animator.translationY(0);
      }
      animator
          .setInterpolator(AnimUtils.EASE_OUT_EASE_IN)
          .setStartDelay(delay)
          .setDuration(duration)
          .setListener(showListener)
          .start();
    }
  }

  public EditText getDigits() {
    return digits;
  }

  public TextView getDigitsHint() {
    return digitsHint;
  }

  public ImageButton getDeleteButton() {
    return delete;
  }

  public View getOverflowMenuButton() {
    return overflowMenuButton;
  }

  /**
   * 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 (isLandscapeMode) {
      if (isRtl) {
        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;
      }
    }

    LogUtil.e(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 (isLandscapeMode) {
      if (isRtl) {
        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;
      }
    }

    LogUtil.e(TAG, "Attempted to get animation duration for invalid key button id.");
    return 0;
  }

  /**
   * An {@link OnPreDrawListener} that adjusts the height/width of each key layout so that they can
   * be properly aligned.
   *
   * <p>When the device is in portrait mode, the layout height for key "1" can be different from
   * those of other <b>digit</b> keys due to the voicemail icon. Adjustments are needed to ensure
   * the layouts for all <b>digit</b> keys are of the same height. Key "*" and key "#" are excluded
   * because their styles are different from other keys'.
   *
   * <p>When the device is in landscape mode, keys can have different layout widths due to the
   * icon/characters associated with them. Adjustments are needed to ensure the layouts for all keys
   * are of the same width.
   *
   * <p>Note that adjustments can only be made after the layouts are measured, which is why the
   * logic lives in an {@link OnPreDrawListener} that is invoked when the view tree is about to be
   * drawn.
   */
  private class OnPreDrawListenerForKeyLayoutAdjust implements OnPreDrawListener {

    /**
     * This method is invoked when the view tree is about to be drawn. At this point, all views in
     * the tree have been measured and given a frame.
     *
     * <p>If the keys have been adjusted, we instruct the current drawing pass to proceed by
     * returning true. Otherwise, adjustments will be made and the current drawing pass will be
     * cancelled by returning false.
     *
     * <p>It is imperative to schedule another layout pass of the view tree after adjustments are
     * made so that {@link #onPreDraw()} can be invoked again to check the layouts and proceed with
     * the drawing pass.
     */
    @Override
    public boolean onPreDraw() {
      if (!shouldAdjustKeySizes()) {
        // Return true to proceed with the current drawing pass.
        // Note that we must NOT remove this listener here. The fact that we don't need to adjust
        // keys at the moment doesn't mean they won't need adjustments in the future. For example,
        // when DialpadFragment is hidden, all keys are of the same size (0) and nothing needs to be
        // done. Removing the listener will cost us the ability to adjust them when they reappear.
        // It is only safe to remove the listener after adjusting keys for the first time. See the
        // comment below for more details.
        return true;
      }

      adjustKeySizes();

      // After all keys are adjusted for the first time, no more adjustments will be needed during
      // the rest of DialpadView's lifecycle. It is therefore safe to remove this listener.
      // Another important reason for removing the listener is that it can be triggered AFTER a
      // device orientation change but BEFORE DialpadView's onDetachedFromWindow() and
      // onFinishInflate() are called, i.e., the listener will attempt to adjust the layout before
      // it is inflated, which results in a crash.
      getViewTreeObserver().removeOnPreDrawListener(this);

      return false; // Return false to cancel the current drawing pass.
    }

    private boolean shouldAdjustKeySizes() {
      return isLandscapeMode ? shouldAdjustKeyWidths() : shouldAdjustDigitKeyHeights();
    }

    /**
     * Return true if not all key layouts have the same width. This method must be called when the
     * device is in landscape mode.
     */
    private boolean shouldAdjustKeyWidths() {
      Assert.checkState(isLandscapeMode);

      DialpadKeyButton dialpadKeyButton = (DialpadKeyButton) findViewById(BUTTON_IDS[0]);
      LinearLayout keyLayout =
          (LinearLayout) dialpadKeyButton.findViewById(R.id.dialpad_key_layout);
      final int width = keyLayout.getWidth();

      for (int i = 1; i < BUTTON_IDS.length; i++) {
        dialpadKeyButton = (DialpadKeyButton) findViewById(BUTTON_IDS[i]);
        keyLayout = (LinearLayout) dialpadKeyButton.findViewById(R.id.dialpad_key_layout);
        if (width != keyLayout.getWidth()) {
          return true;
        }
      }

      return false;
    }

    /**
     * Return true if not all <b>digit</b> key layouts have the same height. This method must be
     * called when the device is in portrait mode.
     */
    private boolean shouldAdjustDigitKeyHeights() {
      Assert.checkState(!isLandscapeMode);

      DialpadKeyButton dialpadKey = (DialpadKeyButton) findViewById(BUTTON_IDS[0]);
      LinearLayout keyLayout = (LinearLayout) dialpadKey.findViewById(R.id.dialpad_key_layout);
      final int height = keyLayout.getHeight();

      // BUTTON_IDS[i] is the resource ID for button i when 0 <= i && i <= 9.
      // For example, BUTTON_IDS[3] is the resource ID for button "3" on the dialpad.
      for (int i = 1; i <= 9; i++) {
        dialpadKey = (DialpadKeyButton) findViewById(BUTTON_IDS[i]);
        keyLayout = (LinearLayout) dialpadKey.findViewById(R.id.dialpad_key_layout);
        if (height != keyLayout.getHeight()) {
          return true;
        }
      }

      return false;
    }

    private void adjustKeySizes() {
      if (isLandscapeMode) {
        adjustKeyWidths();
      } else {
        adjustDigitKeyHeights();
      }
    }

    /**
     * Make the heights of all <b>digit</b> keys the same.
     *
     * <p>When the device is in portrait mode, we first find the maximum height among digit key
     * layouts. Then for each key, we adjust the height of the layout containing letters/the
     * voicemail icon to ensure the height of each digit key is the same.
     *
     * <p>A layout pass will be scheduled in this method by {@link
     * LinearLayout#setLayoutParams(ViewGroup.LayoutParams)}.
     */
    private void adjustDigitKeyHeights() {
      Assert.checkState(!isLandscapeMode);

      int maxHeight = 0;

      // BUTTON_IDS[i] is the resource ID for button i when 0 <= i && i <= 9.
      // For example, BUTTON_IDS[3] is the resource ID for button "3" on the dialpad.
      for (int i = 0; i <= 9; i++) {
        DialpadKeyButton dialpadKey = (DialpadKeyButton) findViewById(BUTTON_IDS[i]);
        LinearLayout keyLayout = (LinearLayout) dialpadKey.findViewById(R.id.dialpad_key_layout);
        maxHeight = Math.max(maxHeight, keyLayout.getHeight());
      }

      for (int i = 0; i <= 9; i++) {
        DialpadKeyButton dialpadKey = (DialpadKeyButton) findViewById(BUTTON_IDS[i]);
        LinearLayout keyLayout = (LinearLayout) dialpadKey.findViewById(R.id.dialpad_key_layout);

        DialpadTextView numberView =
            (DialpadTextView) keyLayout.findViewById(R.id.dialpad_key_number);
        MarginLayoutParams numberViewLayoutParams =
            (MarginLayoutParams) numberView.getLayoutParams();

        LinearLayout iconOrLettersLayout =
            (LinearLayout) keyLayout.findViewById(R.id.dialpad_key_icon_or_letters_layout);
        iconOrLettersLayout.setLayoutParams(
            new LayoutParams(
                LayoutParams.WRAP_CONTENT /* width */,
                maxHeight
                    - numberView.getHeight()
                    - numberViewLayoutParams.topMargin
                    - numberViewLayoutParams.bottomMargin /* height */));
      }
    }

    /**
     * Make the widths of all keys the same.
     *
     * <p>When the device is in landscape mode, we first find the maximum width among key layouts.
     * Then we adjust the width of each layout's horizontal placeholder so that each key has the
     * same width.
     *
     * <p>A layout pass will be scheduled in this method by {@link
     * View#setLayoutParams(ViewGroup.LayoutParams)}.
     */
    private void adjustKeyWidths() {
      Assert.checkState(isLandscapeMode);

      int maxWidth = 0;
      for (int buttonId : BUTTON_IDS) {
        DialpadKeyButton dialpadKey = (DialpadKeyButton) findViewById(buttonId);
        LinearLayout keyLayout = (LinearLayout) dialpadKey.findViewById(R.id.dialpad_key_layout);
        maxWidth = Math.max(maxWidth, keyLayout.getWidth());
      }

      for (int buttonId : BUTTON_IDS) {
        DialpadKeyButton dialpadKey = (DialpadKeyButton) findViewById(buttonId);
        LinearLayout keyLayout = (LinearLayout) dialpadKey.findViewById(R.id.dialpad_key_layout);
        View horizontalPlaceholder =
            keyLayout.findViewById(R.id.dialpad_key_horizontal_placeholder);
        horizontalPlaceholder.setLayoutParams(
            new LayoutParams(
                maxWidth - keyLayout.getWidth() /* width */,
                LayoutParams.MATCH_PARENT /* height */));
      }
    }
  }
}