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);

這種寫法好像挺特別的,不同的狀態設置不同的監聽對象,類似於設計模式裏的狀態模式,值得學習。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章