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