Android Dialer源码分析之IncallUI中SpeakerButton点击后显示音频选择器

前面写了IncallUI的显示过程,只分析到了IncallActivity的起点。

IncallActivity包含了多个Fragment构成,
包含了InCallFragmentInCallButtonGridFragment,SurfaceViewVideoCallFragment,AnswerFragment,VideoCallFragment,等Fragment.
他们的控制显示,则是在各自的Presenter里,通过通话状态来调整。

在这个普通通话界面,包含的是InCallFragmentInCallButtonGridFragment,分别显示上部分的联系人信息通话状态,以及下半部分的通话按键部分。

InCallButtonGridFragment是控制显示通话状态中的Button部分的。
这里的Button都用了代理模式的方式,放在了ButtonController里。

ButtonController的初始化是在InCallFragment里初始化的。这是因为其实刚开始最初显示的时候,InCallButtonGridFragment里面这些button还没有初始化,这些都是空的view,只有InCallFragment在根据状态更新Button的状态时,这些button才会显示出来,所以这些ButtonController都是在InCallFragment里初始化。而且InCallFragment内部持有了InCallButtonGridFragment对象。

看看InCallFragmentonViewCreated

  @Override
  public void onViewCreated(@NonNull View view, @Nullable Bundle bundle) {

...
    buttonControllers.add(new ButtonController.MuteButtonController(inCallButtonUiDelegate));
    buttonControllers.add(new ButtonController.SpeakerButtonController(inCallButtonUiDelegate));
    buttonControllers.add(new ButtonController.DialpadButtonController(inCallButtonUiDelegate));
    buttonControllers.add(new ButtonController.HoldButtonController(inCallButtonUiDelegate));
    buttonControllers.add(new ButtonController.AddCallButtonController(inCallButtonUiDelegate));
    buttonControllers.add(new ButtonController.SwapButtonController(inCallButtonUiDelegate));
    buttonControllers.add(new ButtonController.MergeButtonController(inCallButtonUiDelegate));
...
}

其中SpeakerButtonController有个特别的地方,在系统检测到蓝牙耳机输出的时候,系统会有三个输出声音方式,耳机,外发,和蓝牙,于是就要显示Audio音频选择器界面,而不是自动选择。

SpeakerButtonController里,有两个监听


    @Override
    public void onClick(View v) {
      delegate.showAudioRouteSelector();
    }

    @Override
    public void onCheckedChanged(CheckableLabeledButton checkableLabeledButton, boolean isChecked) {
      checkableLabeledButton.setContentDescription(
          isChecked ? checkedContentDescription : uncheckedContentDescription);
      delegate.toggleSpeakerphone();
    }

一个是当点击View时执行showAudioRouteSelector,去开启音频选择界面,一个是onCheckedChanged去切换外音和喇叭。

捡来的图。。。

这里有个疑问,因为系统代码里没有地方调用showAudioRouteSelector,而要显示音频选择器就一定要调用这个方法,那么就必须是这里的onClick监听回调调用,可是这里又有个onCheckedChanged,要如何区分点击的是onClick还是onCheckedChanged呢?那就要看设置的监听器是谁了。

SpeakerButtonControllersetButton设置监听的时候有判断checkable

    @Override
    public void setButton(CheckableLabeledButton button) {
      this.button = button;
      if (button != null) {
        button.setEnabled(isEnabled && isAllowed);
        button.setVisibility(View.VISIBLE);
        button.setChecked(isChecked);
        button.setOnClickListener(checkable ? null : this);
        button.setOnCheckedChangeListener(checkable ? this : null);
        button.setLabelText(label);
        button.setIconDrawable(icon);
        button.setContentDescription(
            isChecked ? checkedContentDescription : uncheckedContentDescription);
        button.setShouldShowMoreIndicator(!checkable);
      }
    }

原来在设置监听的时候,区分了

        button.setOnClickListener(checkable ? null : this);
        button.setOnCheckedChangeListener(checkable ? this : null);

关键在于checkable ,而checkable 这个值也只有在setAudioState的时候有设置,

public void setAudioState(CallAudioState audioState) {
      SpeakerButtonInfo info = new SpeakerButtonInfo(audioState, IconSize.SIZE_36_DP);
      checkable = info.checkable;
      isChecked = info.isChecked;
      label = info.label;
      icon = info.icon;
      @StringRes int contentDescriptionResId = info.contentDescription;
      contentDescription = delegate.getContext().getText(contentDescriptionResId);
      checkedContentDescription =
          TextUtils.concat(
              contentDescription,
              delegate.getContext().getText(R.string.incall_talkback_speaker_on));
      uncheckedContentDescription =
          TextUtils.concat(
              contentDescription,
              delegate.getContext().getText(R.string.incall_talkback_speaker_off));
      setButton(button);
    }

SpeakerButtonInfo

public SpeakerButtonInfo(CallAudioState audioState, @IconSize int iconSize) {
    if ((audioState.getSupportedRouteMask() & CallAudioState.ROUTE_BLUETOOTH)
        == CallAudioState.ROUTE_BLUETOOTH) {
      checkable = false;
      isChecked = false;
      label = R.string.incall_label_audio;
....

在SpeakerButtonInfo里,当检测到audio支持Bluetooth的时候,就把checkable设为false,然后setButton的时候就分别设置是
ClickListener还是CheckedChangeListener了。

setAudioState的地方是CallButtonPresenter的监听,来自AudioModeListener的回调

  @Override
  public void onAudioStateChanged(CallAudioState audioState) {
    if (mInCallButtonUi != null) {
      mInCallButtonUi.setAudioState(audioState);
    }
  }

setButton的地方在InCallButtonGridFragmentupdateButtonStates最后。

public int updateButtonStates(
      List<ButtonController> buttonControllers,
      @Nullable ButtonChooser buttonChooser,
      int voiceNetworkType,
      int phoneType) {
    Set<Integer> allowedButtons = new ArraySet<>();
    Set<Integer> disabledButtons = new ArraySet<>();
    for (ButtonController controller : buttonControllers) {
      if (controller.isAllowed()) {
        allowedButtons.add(controller.getInCallButtonId());
        if (!controller.isEnabled()) {
          disabledButtons.add(controller.getInCallButtonId());
        }
      }
    }

    for (ButtonController controller : buttonControllers) {
      controller.setButton(null);
    }

    if (buttonChooser == null) {
      buttonChooser =
          ButtonChooserFactory.newButtonChooser(voiceNetworkType, false /* isWiFi */, phoneType);
    }

    int numVisibleButtons = getResources().getInteger(R.integer.incall_num_rows) * BUTTONS_PER_ROW;
    List<Integer> buttonsToPlace =
        buttonChooser.getButtonPlacement(numVisibleButtons, allowedButtons, disabledButtons);

    for (int i = 0; i < BUTTON_COUNT; ++i) {
      if (i >= buttonsToPlace.size()) {
        /// M: set to GONE
        buttons[i].setVisibility(View.GONE);
        continue;
      }
      @InCallButtonIds int button = buttonsToPlace.get(i);
      buttonGridListener.getButtonController(button).setButton(buttons[i]);
    }

    return numVisibleButtons;
  }

这里的buttonGridListener实际对象就是InCallFragment


总结:

  button.setOnClickListener(checkable ? null : this);
  button.setOnCheckedChangeListener(checkable ? this : null);

这种写法好像挺特别的,不同的状态设置不同的监听对象,类似于设计模式里的状态模式,值得学习。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值