1 /*
2  * Copyright (C) 2018 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.incallui.rtt.impl;
18 
19 import android.app.Activity;
20 import android.content.Context;
21 import android.os.Bundle;
22 import android.os.SystemClock;
23 import android.support.annotation.NonNull;
24 import android.support.annotation.Nullable;
25 import android.support.v4.app.Fragment;
26 import android.support.v4.app.FragmentTransaction;
27 import android.support.v7.widget.LinearLayoutManager;
28 import android.support.v7.widget.RecyclerView;
29 import android.support.v7.widget.RecyclerView.OnScrollListener;
30 import android.telecom.CallAudioState;
31 import android.text.Editable;
32 import android.text.TextUtils;
33 import android.text.TextWatcher;
34 import android.view.Gravity;
35 import android.view.KeyEvent;
36 import android.view.LayoutInflater;
37 import android.view.View;
38 import android.view.ViewGroup;
39 import android.view.Window;
40 import android.view.accessibility.AccessibilityEvent;
41 import android.view.inputmethod.EditorInfo;
42 import android.widget.Chronometer;
43 import android.widget.EditText;
44 import android.widget.ImageButton;
45 import android.widget.TextView;
46 import android.widget.TextView.OnEditorActionListener;
47 import com.android.dialer.common.Assert;
48 import com.android.dialer.common.FragmentUtils;
49 import com.android.dialer.common.LogUtil;
50 import com.android.dialer.common.UiUtil;
51 import com.android.dialer.lettertile.LetterTileDrawable;
52 import com.android.dialer.logging.DialerImpression;
53 import com.android.dialer.logging.Logger;
54 import com.android.dialer.rtt.RttTranscript;
55 import com.android.dialer.rtt.RttTranscriptMessage;
56 import com.android.dialer.util.DrawableConverter;
57 import com.android.incallui.audioroute.AudioRouteSelectorDialogFragment.AudioRouteSelectorPresenter;
58 import com.android.incallui.call.state.DialerCallState;
59 import com.android.incallui.hold.OnHoldFragment;
60 import com.android.incallui.incall.protocol.ContactPhotoType;
61 import com.android.incallui.incall.protocol.InCallButtonIds;
62 import com.android.incallui.incall.protocol.InCallButtonUi;
63 import com.android.incallui.incall.protocol.InCallButtonUiDelegate;
64 import com.android.incallui.incall.protocol.InCallButtonUiDelegateFactory;
65 import com.android.incallui.incall.protocol.InCallScreen;
66 import com.android.incallui.incall.protocol.InCallScreenDelegate;
67 import com.android.incallui.incall.protocol.InCallScreenDelegateFactory;
68 import com.android.incallui.incall.protocol.PrimaryCallState;
69 import com.android.incallui.incall.protocol.PrimaryInfo;
70 import com.android.incallui.incall.protocol.SecondaryInfo;
71 import com.android.incallui.rtt.impl.RttChatAdapter.MessageListener;
72 import com.android.incallui.rtt.protocol.Constants;
73 import com.android.incallui.rtt.protocol.RttCallScreen;
74 import com.android.incallui.rtt.protocol.RttCallScreenDelegate;
75 import com.android.incallui.rtt.protocol.RttCallScreenDelegateFactory;
76 import java.util.List;
77 
78 /** RTT chat fragment to show chat bubbles. */
79 public class RttChatFragment extends Fragment
80     implements OnEditorActionListener,
81         TextWatcher,
82         MessageListener,
83         RttCallScreen,
84         InCallScreen,
85         InCallButtonUi,
86         AudioRouteSelectorPresenter {
87 
88   private static final String ARG_CALL_ID = "call_id";
89 
90   private RecyclerView recyclerView;
91   private RttChatAdapter adapter;
92   private EditText editText;
93   private ImageButton submitButton;
94   private boolean isClearingInput;
95 
96   private InCallScreenDelegate inCallScreenDelegate;
97   private RttCallScreenDelegate rttCallScreenDelegate;
98   private InCallButtonUiDelegate inCallButtonUiDelegate;
99   private View endCallButton;
100   private TextView nameTextView;
101   private Chronometer chronometer;
102   private boolean isTimerStarted;
103   private RttOverflowMenu overflowMenu;
104   private SecondaryInfo savedSecondaryInfo;
105   private TextView statusBanner;
106   private PrimaryInfo primaryInfo = PrimaryInfo.empty();
107   private PrimaryCallState primaryCallState = PrimaryCallState.empty();
108   private boolean isUserScrolling;
109   private boolean shouldAutoScrolling;
110   private AudioSelectMenu audioSelectMenu;
111 
112   /**
113    * Create a new instance of RttChatFragment.
114    *
115    * @param callId call id of the RTT call.
116    * @return new RttChatFragment
117    */
newInstance(String callId)118   public static RttChatFragment newInstance(String callId) {
119     Bundle bundle = new Bundle();
120     bundle.putString(ARG_CALL_ID, callId);
121     RttChatFragment instance = new RttChatFragment();
122     instance.setArguments(bundle);
123     return instance;
124   }
125 
126   @Override
onCreate(@ullable Bundle savedInstanceState)127   public void onCreate(@Nullable Bundle savedInstanceState) {
128     super.onCreate(savedInstanceState);
129     LogUtil.i("RttChatFragment.onCreate", null);
130     inCallButtonUiDelegate =
131         FragmentUtils.getParent(this, InCallButtonUiDelegateFactory.class)
132             .newInCallButtonUiDelegate();
133     if (savedInstanceState != null) {
134       inCallButtonUiDelegate.onRestoreInstanceState(savedInstanceState);
135     }
136     inCallScreenDelegate =
137         FragmentUtils.getParentUnsafe(this, InCallScreenDelegateFactory.class)
138             .newInCallScreenDelegate();
139     // Prevent updating local message until UI is ready.
140     isClearingInput = true;
141   }
142 
143   @Override
onViewCreated(@onNull View view, @Nullable Bundle bundle)144   public void onViewCreated(@NonNull View view, @Nullable Bundle bundle) {
145     super.onViewCreated(view, bundle);
146     LogUtil.i("RttChatFragment.onViewCreated", null);
147 
148     rttCallScreenDelegate =
149         FragmentUtils.getParentUnsafe(this, RttCallScreenDelegateFactory.class)
150             .newRttCallScreenDelegate(this);
151 
152     rttCallScreenDelegate.initRttCallScreenDelegate(this);
153 
154     inCallScreenDelegate.onInCallScreenDelegateInit(this);
155     inCallScreenDelegate.onInCallScreenReady();
156     inCallButtonUiDelegate.onInCallButtonUiReady(this);
157   }
158 
159   @Override
getRttTranscriptMessageList()160   public List<RttTranscriptMessage> getRttTranscriptMessageList() {
161     return adapter.getRttTranscriptMessageList();
162   }
163 
164   @Nullable
165   @Override
onCreateView( LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState)166   public View onCreateView(
167       LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
168     View view = inflater.inflate(R.layout.frag_rtt_chat, container, false);
169     view.setSystemUiVisibility(
170         View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
171             | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
172             | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
173             | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
174     editText = view.findViewById(R.id.rtt_chat_input);
175     editText.setOnEditorActionListener(this);
176     editText.addTextChangedListener(this);
177 
178     editText.setOnKeyListener(
179         (v, keyCode, event) -> {
180           // This is only triggered when input method doesn't handle delete key, which usually means
181           // the current input box is empty.
182           // On non-English keyboard delete key could be passed here so we still need to check if
183           // the input box is empty.
184           if (keyCode == KeyEvent.KEYCODE_DEL
185               && event.getAction() == KeyEvent.ACTION_DOWN
186               && TextUtils.isEmpty(editText.getText())) {
187             String lastMessage = adapter.retrieveLastLocalMessage();
188             if (lastMessage != null) {
189               resumeInput(lastMessage);
190               rttCallScreenDelegate.onLocalMessage("\b");
191               return true;
192             }
193             return false;
194           }
195           return false;
196         });
197     recyclerView = view.findViewById(R.id.rtt_recycler_view);
198     LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
199     layoutManager.setStackFromEnd(true);
200     recyclerView.setLayoutManager(layoutManager);
201     recyclerView.setHasFixedSize(false);
202     adapter = new RttChatAdapter(getContext(), this);
203     recyclerView.setAdapter(adapter);
204     recyclerView.addOnScrollListener(
205         new OnScrollListener() {
206           @Override
207           public void onScrollStateChanged(RecyclerView recyclerView, int i) {
208             if (i == RecyclerView.SCROLL_STATE_DRAGGING) {
209               isUserScrolling = true;
210             } else if (i == RecyclerView.SCROLL_STATE_IDLE) {
211               isUserScrolling = false;
212               // Auto scrolling for new messages should be resumed if it's scrolled to bottom.
213               shouldAutoScrolling = !recyclerView.canScrollVertically(1);
214             }
215           }
216 
217           @Override
218           public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
219             if (dy < 0 && isUserScrolling) {
220               UiUtil.hideKeyboardFrom(getContext(), editText);
221             }
222           }
223         });
224 
225     submitButton = view.findViewById(R.id.rtt_chat_submit_button);
226     submitButton.setOnClickListener(
227         v -> {
228           Logger.get(getContext()).logImpression(DialerImpression.Type.RTT_SEND_BUTTON_CLICKED);
229           adapter.submitLocalMessage();
230           resumeInput("");
231           rttCallScreenDelegate.onLocalMessage(Constants.BUBBLE_BREAKER);
232           // Auto scrolling for new messages should be resumed since user has submit current
233           // message.
234           shouldAutoScrolling = true;
235         });
236     submitButton.setEnabled(false);
237     endCallButton = view.findViewById(R.id.rtt_end_call_button);
238     endCallButton.setOnClickListener(
239         v -> {
240           LogUtil.i("RttChatFragment.onClick", "end call button clicked");
241           inCallButtonUiDelegate.onEndCallClicked();
242         });
243 
244     overflowMenu = new RttOverflowMenu(getContext(), inCallButtonUiDelegate, inCallScreenDelegate);
245     view.findViewById(R.id.rtt_overflow_button)
246         .setOnClickListener(
247             v -> {
248               // Hide keyboard when opening overflow menu. This is alternative solution since hiding
249               // keyboard after the menu is open or dialpad is shown doesn't work.
250               UiUtil.hideKeyboardFrom(getContext(), editText);
251               overflowMenu.showAtLocation(v, Gravity.TOP | Gravity.RIGHT, 0, 0);
252             });
253 
254     nameTextView = view.findViewById(R.id.rtt_name_or_number);
255     chronometer = view.findViewById(R.id.rtt_timer);
256     statusBanner = view.findViewById(R.id.rtt_status_banner);
257     return view;
258   }
259 
260   @Override
onEditorAction(TextView v, int actionId, KeyEvent event)261   public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
262     if (actionId == EditorInfo.IME_ACTION_SEND) {
263       if (!TextUtils.isEmpty(editText.getText())) {
264         Logger.get(getContext())
265             .logImpression(DialerImpression.Type.RTT_KEYBOARD_SEND_BUTTON_CLICKED);
266         submitButton.performClick();
267       }
268       return true;
269     }
270     return false;
271   }
272 
273   @Override
beforeTextChanged(CharSequence s, int start, int count, int after)274   public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
275 
276   @Override
onTextChanged(CharSequence s, int start, int before, int count)277   public void onTextChanged(CharSequence s, int start, int before, int count) {
278     if (isClearingInput) {
279       return;
280     }
281     String messageToAppend = adapter.computeChangeOfLocalMessage(s.toString());
282     if (!TextUtils.isEmpty(messageToAppend)) {
283       adapter.addLocalMessage(messageToAppend);
284       rttCallScreenDelegate.onLocalMessage(messageToAppend);
285     }
286   }
287 
288   @Override
onRemoteMessage(String message)289   public void onRemoteMessage(String message) {
290     adapter.addRemoteMessage(message);
291   }
292 
293   @Override
onDestroyView()294   public void onDestroyView() {
295     super.onDestroyView();
296     LogUtil.enterBlock("RttChatFragment.onDestroyView");
297     inCallButtonUiDelegate.onInCallButtonUiUnready();
298     inCallScreenDelegate.onInCallScreenUnready();
299   }
300 
301   @Override
afterTextChanged(Editable s)302   public void afterTextChanged(Editable s) {
303     if (TextUtils.isEmpty(s)) {
304       submitButton.setEnabled(false);
305     } else {
306       submitButton.setEnabled(true);
307     }
308   }
309 
310   @Override
onUpdateLocalMessage(int position)311   public void onUpdateLocalMessage(int position) {
312     if (position < 0) {
313       return;
314     }
315     recyclerView.smoothScrollToPosition(position);
316   }
317 
318   @Override
onUpdateRemoteMessage(int position)319   public void onUpdateRemoteMessage(int position) {
320     if (position < 0) {
321       return;
322     }
323     if (shouldAutoScrolling) {
324       recyclerView.smoothScrollToPosition(position);
325     }
326   }
327 
328   @Override
onRestoreRttChat(RttTranscript rttTranscript)329   public void onRestoreRttChat(RttTranscript rttTranscript) {
330     String unfinishedLocalMessage = adapter.onRestoreRttChat(rttTranscript);
331     if (unfinishedLocalMessage != null) {
332       resumeInput(unfinishedLocalMessage);
333     }
334   }
335 
resumeInput(String input)336   private void resumeInput(String input) {
337     isClearingInput = true;
338     editText.setText(input);
339     editText.setSelection(input.length());
340     isClearingInput = false;
341   }
342 
343   @Override
onStart()344   public void onStart() {
345     LogUtil.enterBlock("RttChatFragment.onStart");
346     super.onStart();
347     isClearingInput = false;
348     onRttScreenStart();
349   }
350 
351   @Override
onStop()352   public void onStop() {
353     LogUtil.enterBlock("RttChatFragment.onStop");
354     super.onStop();
355     isClearingInput = true;
356     if (overflowMenu.isShowing()) {
357       overflowMenu.dismiss();
358     }
359     onRttScreenStop();
360   }
361 
362   @Override
onRttScreenStart()363   public void onRttScreenStart() {
364     rttCallScreenDelegate.onRttCallScreenUiReady();
365     Activity activity = getActivity();
366     Window window = getActivity().getWindow();
367     window.setStatusBarColor(activity.getColor(R.color.rtt_status_bar_color));
368     window.setNavigationBarColor(activity.getColor(R.color.rtt_navigation_bar_color));
369   }
370 
371   @Override
onRttScreenStop()372   public void onRttScreenStop() {
373     Activity activity = getActivity();
374     Window window = getActivity().getWindow();
375     window.setStatusBarColor(activity.getColor(android.R.color.transparent));
376     window.setNavigationBarColor(activity.getColor(android.R.color.transparent));
377     rttCallScreenDelegate.onRttCallScreenUiUnready();
378   }
379 
380   @Override
getRttCallScreenFragment()381   public Fragment getRttCallScreenFragment() {
382     return this;
383   }
384 
385   @Override
getCallId()386   public String getCallId() {
387     return Assert.isNotNull(getArguments().getString(ARG_CALL_ID));
388   }
389 
390   @Override
setPrimary(@onNull PrimaryInfo primaryInfo)391   public void setPrimary(@NonNull PrimaryInfo primaryInfo) {
392     LogUtil.i("RttChatFragment.setPrimary", primaryInfo.toString());
393     nameTextView.setText(primaryInfo.name());
394     updateAvatar(primaryInfo);
395     this.primaryInfo = primaryInfo;
396   }
397 
updateAvatar(PrimaryInfo primaryInfo)398   private void updateAvatar(PrimaryInfo primaryInfo) {
399     boolean hasPhoto =
400         primaryInfo.photo() != null && primaryInfo.photoType() == ContactPhotoType.CONTACT;
401     // Contact has a photo, don't render a letter tile.
402     if (hasPhoto) {
403       int avatarSize = getResources().getDimensionPixelSize(R.dimen.rtt_avatar_size);
404       adapter.setAvatarDrawable(
405           DrawableConverter.getRoundedDrawable(
406               getContext(), primaryInfo.photo(), avatarSize, avatarSize));
407     } else {
408       LetterTileDrawable letterTile = new LetterTileDrawable(getResources());
409       letterTile.setCanonicalDialerLetterTileDetails(
410           primaryInfo.name(),
411           primaryInfo.contactInfoLookupKey(),
412           LetterTileDrawable.SHAPE_CIRCLE,
413           LetterTileDrawable.getContactTypeFromPrimitives(
414               primaryCallState.isVoiceMailNumber(),
415               primaryInfo.isSpam(),
416               primaryCallState.isBusinessNumber(),
417               primaryInfo.numberPresentation(),
418               primaryCallState.isConference()));
419       adapter.setAvatarDrawable(letterTile);
420     }
421   }
422 
423   @Override
onAttach(Context context)424   public void onAttach(Context context) {
425     super.onAttach(context);
426     if (savedSecondaryInfo != null) {
427       setSecondary(savedSecondaryInfo);
428     }
429   }
430 
431   @Override
setSecondary(@onNull SecondaryInfo secondaryInfo)432   public void setSecondary(@NonNull SecondaryInfo secondaryInfo) {
433     LogUtil.i("RttChatFragment.setSecondary", secondaryInfo.toString());
434     if (!isAdded()) {
435       savedSecondaryInfo = secondaryInfo;
436       return;
437     }
438     savedSecondaryInfo = null;
439     FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
440     Fragment oldBanner = getChildFragmentManager().findFragmentById(R.id.rtt_on_hold_banner);
441     if (secondaryInfo.shouldShow()) {
442       OnHoldFragment onHoldFragment = OnHoldFragment.newInstance(secondaryInfo);
443       onHoldFragment.setPadTopInset(false);
444       transaction.replace(R.id.rtt_on_hold_banner, onHoldFragment);
445     } else {
446       if (oldBanner != null) {
447         transaction.remove(oldBanner);
448       }
449     }
450     transaction.setCustomAnimations(R.anim.abc_slide_in_top, R.anim.abc_slide_out_top);
451     transaction.commitNowAllowingStateLoss();
452     overflowMenu.enableSwitchToSecondaryButton(secondaryInfo.shouldShow());
453   }
454 
455   @Override
setCallState(@onNull PrimaryCallState primaryCallState)456   public void setCallState(@NonNull PrimaryCallState primaryCallState) {
457     LogUtil.i("RttChatFragment.setCallState", primaryCallState.toString());
458     this.primaryCallState = primaryCallState;
459     if (!isTimerStarted && primaryCallState.state() == DialerCallState.ACTIVE) {
460       LogUtil.i(
461           "RttChatFragment.setCallState", "starting timer with base: %d", chronometer.getBase());
462       chronometer.setBase(
463           primaryCallState.connectTimeMillis()
464               - System.currentTimeMillis()
465               + SystemClock.elapsedRealtime());
466       chronometer.start();
467       isTimerStarted = true;
468       editText.setVisibility(View.VISIBLE);
469       submitButton.setVisibility(View.VISIBLE);
470       editText.setFocusableInTouchMode(true);
471       if (editText.requestFocus()) {
472         UiUtil.showKeyboardFrom(getContext(), editText);
473       }
474       adapter.showAdvisory();
475     }
476     if (primaryCallState.state() == DialerCallState.DIALING) {
477       showWaitingForJoinBanner();
478     } else {
479       hideWaitingForJoinBanner();
480     }
481     if (primaryCallState.state() == DialerCallState.DISCONNECTED) {
482       rttCallScreenDelegate.onSaveRttTranscript();
483     }
484   }
485 
showWaitingForJoinBanner()486   private void showWaitingForJoinBanner() {
487     statusBanner.setText(getString(R.string.rtt_status_banner_text, primaryInfo.name()));
488     statusBanner.setVisibility(View.VISIBLE);
489   }
490 
hideWaitingForJoinBanner()491   private void hideWaitingForJoinBanner() {
492     statusBanner.setVisibility(View.GONE);
493   }
494 
495   @Override
setEndCallButtonEnabled(boolean enabled, boolean animate)496   public void setEndCallButtonEnabled(boolean enabled, boolean animate) {}
497 
498   @Override
showManageConferenceCallButton(boolean visible)499   public void showManageConferenceCallButton(boolean visible) {}
500 
501   @Override
isManageConferenceVisible()502   public boolean isManageConferenceVisible() {
503     return false;
504   }
505 
506   @Override
dispatchPopulateAccessibilityEvent(AccessibilityEvent event)507   public void dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {}
508 
509   @Override
showNoteSentToast()510   public void showNoteSentToast() {}
511 
512   @Override
updateInCallScreenColors()513   public void updateInCallScreenColors() {}
514 
515   @Override
onInCallScreenDialpadVisibilityChange(boolean isShowing)516   public void onInCallScreenDialpadVisibilityChange(boolean isShowing) {
517     overflowMenu.setDialpadButtonChecked(isShowing);
518   }
519 
520   @Override
getAnswerAndDialpadContainerResourceId()521   public int getAnswerAndDialpadContainerResourceId() {
522     return R.id.incall_dialpad_container;
523   }
524 
525   @Override
showLocationUi(Fragment locationUi)526   public void showLocationUi(Fragment locationUi) {}
527 
528   @Override
isShowingLocationUi()529   public boolean isShowingLocationUi() {
530     return false;
531   }
532 
533   @Override
getInCallScreenFragment()534   public Fragment getInCallScreenFragment() {
535     return this;
536   }
537 
538   @Override
showButton(int buttonId, boolean show)539   public void showButton(int buttonId, boolean show) {
540     if (buttonId == InCallButtonIds.BUTTON_SWAP) {
541       overflowMenu.enableSwapCallButton(show);
542     }
543   }
544 
545   @Override
enableButton(int buttonId, boolean enable)546   public void enableButton(int buttonId, boolean enable) {}
547 
548   @Override
setEnabled(boolean on)549   public void setEnabled(boolean on) {}
550 
551   @Override
setHold(boolean on)552   public void setHold(boolean on) {}
553 
554   @Override
setCameraSwitched(boolean isBackFacingCamera)555   public void setCameraSwitched(boolean isBackFacingCamera) {}
556 
557   @Override
setVideoPaused(boolean isPaused)558   public void setVideoPaused(boolean isPaused) {}
559 
560   @Override
setAudioState(CallAudioState audioState)561   public void setAudioState(CallAudioState audioState) {
562     LogUtil.i("RttChatFragment.setAudioState", "audioState: " + audioState);
563     overflowMenu.setMuteButtonChecked(audioState.isMuted());
564     overflowMenu.setAudioState(audioState);
565     if (audioSelectMenu != null) {
566       audioSelectMenu.setAudioState(audioState);
567     }
568   }
569 
570   @Override
updateButtonStates()571   public void updateButtonStates() {}
572 
573   @Override
updateInCallButtonUiColors(int color)574   public void updateInCallButtonUiColors(int color) {}
575 
576   @Override
getInCallButtonUiFragment()577   public Fragment getInCallButtonUiFragment() {
578     return this;
579   }
580 
581   @Override
showAudioRouteSelector()582   public void showAudioRouteSelector() {
583     audioSelectMenu =
584         new AudioSelectMenu(
585             getContext(),
586             inCallButtonUiDelegate,
587             () -> overflowMenu.showAtLocation(getView(), Gravity.TOP | Gravity.RIGHT, 0, 0));
588     audioSelectMenu.showAtLocation(getView(), Gravity.TOP | Gravity.RIGHT, 0, 0);
589   }
590 
591   @Override
onAudioRouteSelected(int audioRoute)592   public void onAudioRouteSelected(int audioRoute) {
593     inCallButtonUiDelegate.setAudioRoute(audioRoute);
594   }
595 
596   @Override
onAudioRouteSelectorDismiss()597   public void onAudioRouteSelectorDismiss() {}
598 }
599