1 /* 2 * Copyright (C) 2021 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.server.telecom; 18 19 import android.Manifest; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.RequiresPermission; 23 import android.annotation.UserIdInt; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.ServiceConnection; 28 import android.content.pm.ResolveInfo; 29 import android.os.Bundle; 30 import android.os.IBinder; 31 import android.os.RemoteException; 32 import android.os.UserHandle; 33 import android.telecom.BluetoothCallQualityReport; 34 import android.telecom.CallAudioState; 35 import android.telecom.CallDiagnosticService; 36 import android.telecom.ConnectionService; 37 import android.telecom.CallDiagnostics; 38 import android.telecom.DisconnectCause; 39 import android.telecom.InCallService; 40 import android.telecom.Log; 41 import android.telecom.ParcelableCall; 42 import android.telephony.CallQuality; 43 import android.telephony.ims.ImsReasonInfo; 44 import android.text.TextUtils; 45 46 import com.android.internal.telecom.ICallDiagnosticService; 47 import com.android.internal.util.IndentingPrintWriter; 48 49 import java.util.List; 50 51 /** 52 * Responsible for maintaining binding to the {@link CallDiagnosticService} defined by the 53 * {@code call_diagnostic_service_package_name} key in the 54 * {@code packages/services/Telecomm/res/values/config.xml} file. 55 */ 56 public class CallDiagnosticServiceController extends CallsManagerListenerBase { 57 /** 58 * Context dependencies for the {@link CallDiagnosticServiceController}. 59 */ 60 public interface ContextProxy { queryIntentServicesAsUser(@onNull Intent intent, int resolveInfoFlags, @UserIdInt int userId)61 List<ResolveInfo> queryIntentServicesAsUser(@NonNull Intent intent, 62 int resolveInfoFlags, @UserIdInt int userId); bindServiceAsUser(@onNull @equiresPermission Intent service, @NonNull ServiceConnection conn, int flags, @NonNull UserHandle user)63 boolean bindServiceAsUser(@NonNull @RequiresPermission Intent service, 64 @NonNull ServiceConnection conn, int flags, @NonNull UserHandle user); unbindService(@onNull ServiceConnection conn)65 void unbindService(@NonNull ServiceConnection conn); getCurrentUserHandle()66 UserHandle getCurrentUserHandle(); 67 } 68 69 /** 70 * Listener for {@link Call} events; used to propagate these changes to the 71 * {@link CallDiagnosticService}. 72 */ 73 private final Call.Listener mCallListener = new Call.ListenerBase() { 74 @Override 75 public void onConnectionCapabilitiesChanged(Call call) { 76 updateCall(call); 77 } 78 79 @Override 80 public void onConnectionPropertiesChanged(Call call, boolean didRttChange) { 81 updateCall(call); 82 } 83 84 /** 85 * Listens for changes to extras reported by a Telecom {@link Call}. 86 * 87 * Extras changes can originate from a {@link ConnectionService} or an {@link InCallService} 88 * so we will only trigger an update of the call information if the source of the 89 * extras change was a {@link ConnectionService}. 90 * 91 * @param call The call. 92 * @param source The source of the extras change 93 * ({@link Call#SOURCE_CONNECTION_SERVICE} or 94 * {@link Call#SOURCE_INCALL_SERVICE}). 95 * @param extras The extras. 96 */ 97 @Override 98 public void onExtrasChanged(Call call, int source, Bundle extras, 99 String requestingPackageName) { 100 // Do not inform of changes which originated from an InCallService to a CDS. 101 if (source == Call.SOURCE_INCALL_SERVICE) { 102 return; 103 } 104 updateCall(call); 105 } 106 107 /** 108 * Listens for changes to extras reported by a Telecom {@link Call}. 109 * 110 * Extras changes can originate from a {@link ConnectionService} or an {@link InCallService} 111 * so we will only trigger an update of the call information if the source of the extras 112 * change was a {@link ConnectionService}. 113 * @param call The call. 114 * @param source The source of the extras change ({@link Call#SOURCE_CONNECTION_SERVICE} or 115 * {@link Call#SOURCE_INCALL_SERVICE}). 116 * @param keys The extra key removed 117 */ 118 @Override 119 public void onExtrasRemoved(Call call, int source, List<String> keys) { 120 // Do not inform InCallServices of changes which originated there. 121 if (source == Call.SOURCE_INCALL_SERVICE) { 122 return; 123 } 124 updateCall(call); 125 } 126 127 /** 128 * Handles changes to the video state of a call. 129 * @param call 130 * @param previousVideoState 131 * @param newVideoState 132 */ 133 @Override 134 public void onVideoStateChanged(Call call, int previousVideoState, int newVideoState) { 135 updateCall(call); 136 } 137 138 /** 139 * Relays a bluetooth call quality report received from the Bluetooth stack to the 140 * CallDiagnosticService. 141 * @param call The call. 142 * @param report The received report. 143 */ 144 @Override 145 public void onBluetoothCallQualityReport(Call call, BluetoothCallQualityReport report) { 146 handleBluetoothCallQualityReport(call, report); 147 } 148 149 /** 150 * Relays a device to device message received from Telephony to the CallDiagnosticService. 151 * @param call 152 * @param messageType 153 * @param messageValue 154 */ 155 @Override 156 public void onReceivedDeviceToDeviceMessage(Call call, int messageType, int messageValue) { 157 handleReceivedDeviceToDeviceMessage(call, messageType, messageValue); 158 } 159 160 /** 161 * Handles an incoming {@link CallQuality} report from a {@link android.telecom.Connection}. 162 * @param call The call. 163 * @param callQualityReport The call quality report. 164 */ 165 @Override 166 public void onReceivedCallQualityReport(Call call, CallQuality callQualityReport) { 167 handleCallQualityReport(call, callQualityReport); 168 } 169 }; 170 171 /** 172 * {@link ServiceConnection} handling changes to binding of the {@link CallDiagnosticService}. 173 */ 174 private class CallDiagnosticServiceConnection implements ServiceConnection { 175 @Override onServiceConnected(ComponentName name, IBinder service)176 public void onServiceConnected(ComponentName name, IBinder service) { 177 Log.startSession("CDSC.oSC", Log.getPackageAbbreviation(name)); 178 try { 179 synchronized (mLock) { 180 mCallDiagnosticService = ICallDiagnosticService.Stub.asInterface(service); 181 182 handleConnectionComplete(mCallDiagnosticService); 183 } 184 Log.i(CallDiagnosticServiceController.this, "onServiceConnected: cmp=%s", name); 185 } finally { 186 Log.endSession(); 187 } 188 } 189 190 @Override onServiceDisconnected(ComponentName name)191 public void onServiceDisconnected(ComponentName name) { 192 Log.startSession("CDSC.oSD", Log.getPackageAbbreviation(name)); 193 try { 194 synchronized (mLock) { 195 mCallDiagnosticService = null; 196 mConnection = null; 197 } 198 Log.i(CallDiagnosticServiceController.this, "onServiceDisconnected: cmp=%s", name); 199 } finally { 200 Log.endSession(); 201 } 202 } 203 204 @Override onBindingDied(ComponentName name)205 public void onBindingDied(ComponentName name) { 206 Log.startSession("CDSC.oBD", Log.getPackageAbbreviation(name)); 207 try { 208 synchronized (mLock) { 209 mCallDiagnosticService = null; 210 mConnection = null; 211 } 212 Log.w(CallDiagnosticServiceController.this, "onBindingDied: cmp=%s", name); 213 } finally { 214 Log.endSession(); 215 } 216 } 217 218 @Override onNullBinding(ComponentName name)219 public void onNullBinding(ComponentName name) { 220 Log.startSession("CDSC.oNB", Log.getPackageAbbreviation(name)); 221 try { 222 synchronized (mLock) { 223 maybeUnbindCallScreeningService(); 224 } 225 } finally { 226 Log.endSession(); 227 } 228 } 229 } 230 231 private final String mPackageName; 232 private final ContextProxy mContextProxy; 233 private InCallTonePlayer.Factory mPlayerFactory; 234 private String mTestPackageName; 235 private CallDiagnosticServiceConnection mConnection; 236 private CallDiagnosticServiceAdapter mAdapter; 237 private final TelecomSystem.SyncRoot mLock; 238 private ICallDiagnosticService mCallDiagnosticService; 239 private final CallIdMapper mCallIdMapper = new CallIdMapper(Call::getId); 240 CallDiagnosticServiceController(@onNull ContextProxy contextProxy, @Nullable String packageName, @NonNull TelecomSystem.SyncRoot lock)241 public CallDiagnosticServiceController(@NonNull ContextProxy contextProxy, 242 @Nullable String packageName, @NonNull TelecomSystem.SyncRoot lock) { 243 mContextProxy = contextProxy; 244 mPackageName = packageName; 245 mLock = lock; 246 } 247 248 /** 249 * Sets the current {@link InCallTonePlayer.Factory} for this instance. 250 * @param factory the factory. 251 */ setInCallTonePlayerFactory(InCallTonePlayer.Factory factory)252 public void setInCallTonePlayerFactory(InCallTonePlayer.Factory factory) { 253 mPlayerFactory = factory; 254 } 255 256 /** 257 * Handles Telecom adding new calls. Will bind to the call diagnostic service if needed and 258 * send the calls, or send to an already bound service. 259 * @param call The call to add. 260 */ 261 @Override onCallAdded(@onNull Call call)262 public void onCallAdded(@NonNull Call call) { 263 if (!call.isSimCall() || call.isExternalCall()) { 264 Log.i(this, "onCallAdded: skipping call %s as non-sim or external.", call.getId()); 265 return; 266 } 267 if (mCallIdMapper.getCallId(call) == null) { 268 mCallIdMapper.addCall(call); 269 call.addListener(mCallListener); 270 } 271 if (isConnected()) { 272 sendCallToBoundService(call, mCallDiagnosticService); 273 } else { 274 maybeBindCallDiagnosticService(); 275 } 276 } 277 278 /** 279 * Handles a newly disconnected call signalled from {@link CallsManager}. 280 * @param call The call 281 * @param disconnectCause The disconnect cause 282 * @return {@code true} if the {@link CallDiagnosticService} was sent the call, {@code false} 283 * if the call was not applicable to the CDS or if there was an issue sending it. 284 */ onCallDisconnected(@onNull Call call, @NonNull DisconnectCause disconnectCause)285 public boolean onCallDisconnected(@NonNull Call call, 286 @NonNull DisconnectCause disconnectCause) { 287 if (!call.isSimCall() || call.isExternalCall()) { 288 Log.i(this, "onCallDisconnected: skipping call %s as non-sim or external.", 289 call.getId()); 290 return false; 291 } 292 String callId = mCallIdMapper.getCallId(call); 293 try { 294 if (isConnected()) { 295 mCallDiagnosticService.notifyCallDisconnected(callId, disconnectCause); 296 return true; 297 } 298 } catch (RemoteException e) { 299 Log.w(this, "onCallDisconnected: callId=%s, exception=%s", call.getId(), e); 300 } 301 return false; 302 } 303 304 /** 305 * Handles Telecom removal of calls; will remove the call from the bound service and if the 306 * number of tracked calls falls to zero, unbind from the service. 307 * @param call The call to remove from the bound CDS. 308 */ 309 @Override onCallRemoved(@onNull Call call)310 public void onCallRemoved(@NonNull Call call) { 311 if (!call.isSimCall() || call.isExternalCall()) { 312 Log.i(this, "onCallRemoved: skipping call %s as non-sim or external.", call.getId()); 313 return; 314 } 315 mCallIdMapper.removeCall(call); 316 call.removeListener(mCallListener); 317 removeCallFromBoundService(call, mCallDiagnosticService); 318 319 if (mCallIdMapper.getCalls().size() == 0) { 320 maybeUnbindCallScreeningService(); 321 } 322 } 323 324 @Override onCallStateChanged(Call call, int oldState, int newState)325 public void onCallStateChanged(Call call, int oldState, int newState) { 326 updateCall(call); 327 } 328 329 @Override onCallAudioStateChanged(CallAudioState oldCallAudioState, CallAudioState newCallAudioState)330 public void onCallAudioStateChanged(CallAudioState oldCallAudioState, 331 CallAudioState newCallAudioState) { 332 if (mCallDiagnosticService != null) { 333 try { 334 mCallDiagnosticService.updateCallAudioState(newCallAudioState); 335 } catch (RemoteException e) { 336 Log.w(this, "onCallAudioStateChanged: failed %s", e); 337 } 338 } 339 } 340 341 /** 342 * Sets the test call diagnostic service; used by the telecom command line command to override 343 * the {@link CallDiagnosticService} to bind to for CTS test purposes. 344 * @param packageName The package name to set to. 345 */ setTestCallDiagnosticService(@ullable String packageName)346 public void setTestCallDiagnosticService(@Nullable String packageName) { 347 if (TextUtils.isEmpty(packageName)) { 348 mTestPackageName = null; 349 } else { 350 mTestPackageName = packageName; 351 } 352 353 Log.i(this, "setTestCallDiagnosticService: packageName=%s", packageName); 354 } 355 356 /** 357 * Determines the active call diagnostic service, taking into account the test override. 358 * @return The package name of the active call diagnostic service. 359 */ getActiveCallDiagnosticService()360 private @Nullable String getActiveCallDiagnosticService() { 361 if (mTestPackageName != null) { 362 return mTestPackageName; 363 } 364 365 return mPackageName; 366 } 367 368 /** 369 * If we are not already bound to the {@link CallDiagnosticService}, attempts to initiate a 370 * binding tho that service. 371 * @return {@code true} if we bound, {@code false} otherwise. 372 */ maybeBindCallDiagnosticService()373 private boolean maybeBindCallDiagnosticService() { 374 if (mConnection != null) { 375 return false; 376 } 377 378 mConnection = new CallDiagnosticServiceConnection(); 379 boolean bound = bindCallDiagnosticService(mContextProxy.getCurrentUserHandle(), 380 getActiveCallDiagnosticService(), mConnection); 381 if (!bound) { 382 mConnection = null; 383 } 384 return bound; 385 } 386 387 /** 388 * Performs binding to the {@link CallDiagnosticService}. 389 * @param userHandle user name to bind via. 390 * @param packageName package name of the CDS. 391 * @param serviceConnection The service connection to be notified of bind events. 392 * @return 393 */ bindCallDiagnosticService(UserHandle userHandle, String packageName, CallDiagnosticServiceConnection serviceConnection)394 private boolean bindCallDiagnosticService(UserHandle userHandle, 395 String packageName, CallDiagnosticServiceConnection serviceConnection) { 396 397 if (TextUtils.isEmpty(packageName)) { 398 Log.i(this, "bindCallDiagnosticService: no package; skip binding."); 399 return false; 400 } 401 402 Intent intent = new Intent(CallDiagnosticService.SERVICE_INTERFACE) 403 .setPackage(packageName); 404 Log.i(this, "bindCallDiagnosticService: user %d.", userHandle.getIdentifier()); 405 List<ResolveInfo> entries = mContextProxy.queryIntentServicesAsUser(intent, 0, 406 userHandle.getIdentifier()); 407 if (entries.isEmpty()) { 408 Log.i(this, "bindCallDiagnosticService: %s has no service.", packageName); 409 return false; 410 } 411 412 ResolveInfo entry = entries.get(0); 413 if (entry.serviceInfo == null) { 414 Log.i(this, "bindCallDiagnosticService: %s has no service info.", packageName); 415 return false; 416 } 417 418 if (entry.serviceInfo.permission == null || !entry.serviceInfo.permission.equals( 419 Manifest.permission.BIND_CALL_DIAGNOSTIC_SERVICE)) { 420 Log.i(this, "bindCallDiagnosticService: %s doesn't require " 421 + "BIND_CALL_DIAGNOSTIC_SERVICE.", packageName); 422 return false; 423 } 424 425 ComponentName componentName = 426 new ComponentName(entry.serviceInfo.packageName, entry.serviceInfo.name); 427 intent.setComponent(componentName); 428 if (mContextProxy.bindServiceAsUser( 429 intent, 430 serviceConnection, 431 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, 432 UserHandle.CURRENT)) { 433 Log.d(this, "bindCallDiagnosticService, found service, waiting for it to connect"); 434 return true; 435 } 436 return false; 437 } 438 439 /** 440 * If we are bound to a {@link CallDiagnosticService}, unbind from it. 441 */ maybeUnbindCallScreeningService()442 public void maybeUnbindCallScreeningService() { 443 if (mConnection != null) { 444 Log.i(this, "maybeUnbindCallScreeningService - unbinding from %s", 445 getActiveCallDiagnosticService()); 446 try { 447 mContextProxy.unbindService(mConnection); 448 mCallDiagnosticService = null; 449 mConnection = null; 450 } catch (IllegalArgumentException e) { 451 Log.i(this, "maybeUnbindCallScreeningService: Exception when unbind %s : %s", 452 getActiveCallDiagnosticService(), e.getMessage()); 453 } 454 } else { 455 Log.w(this, "maybeUnbindCallScreeningService - already unbound"); 456 } 457 } 458 459 /** 460 * Implements the abstracted Telecom functionality the {@link CallDiagnosticServiceAdapter} 461 * depends on. 462 */ 463 private CallDiagnosticServiceAdapter.TelecomAdapter mTelecomAdapter = 464 new CallDiagnosticServiceAdapter.TelecomAdapter() { 465 466 @Override 467 public void displayDiagnosticMessage(String callId, int messageId, CharSequence message) { 468 handleDisplayDiagnosticMessage(callId, messageId, message); 469 } 470 471 @Override 472 public void clearDiagnosticMessage(String callId, int messageId) { 473 handleClearDiagnosticMessage(callId, messageId); 474 } 475 476 @Override 477 public void sendDeviceToDeviceMessage(String callId, 478 @CallDiagnostics.MessageType int message, int value) { 479 handleSendD2DMessage(callId, message, value); 480 } 481 482 @Override 483 public void overrideDisconnectMessage(String callId, CharSequence message) { 484 handleOverrideDisconnectMessage(callId, message); 485 } 486 }; 487 488 /** 489 * Sends all calls to the specified {@link CallDiagnosticService}. 490 * @param callDiagnosticService the CDS to send calls to. 491 */ handleConnectionComplete(@onNull ICallDiagnosticService callDiagnosticService)492 private void handleConnectionComplete(@NonNull ICallDiagnosticService callDiagnosticService) { 493 mAdapter = new CallDiagnosticServiceAdapter(mTelecomAdapter, 494 getActiveCallDiagnosticService(), mLock); 495 try { 496 // Add adapter for communication back from the call diagnostic service to Telecom. 497 callDiagnosticService.setAdapter(mAdapter); 498 499 // Loop through all the calls we've got ready to send since binding. 500 for (Call call : mCallIdMapper.getCalls()) { 501 sendCallToBoundService(call, callDiagnosticService); 502 } 503 } catch (RemoteException e) { 504 Log.w(this, "handleConnectionComplete: error=%s", e); 505 } 506 } 507 508 /** 509 * Handles a request from a {@link CallDiagnosticService} to display a diagnostic message. 510 * @param callId the ID of the call to display the message for. 511 * @param message the message. 512 */ handleDisplayDiagnosticMessage(@onNull String callId, int messageId, @Nullable CharSequence message)513 private void handleDisplayDiagnosticMessage(@NonNull String callId, int messageId, 514 @Nullable CharSequence message) { 515 Call call = mCallIdMapper.getCall(callId); 516 if (call == null) { 517 Log.w(this, "handleDisplayDiagnosticMessage: callId=%s; msg=%d/%s; invalid call", 518 callId, messageId, message); 519 return; 520 } 521 Log.i(this, "handleDisplayDiagnosticMessage: callId=%s; msg=%d/%s", 522 callId, messageId, message); 523 if (mPlayerFactory != null) { 524 // Play that tone! 525 mPlayerFactory.createPlayer(call, InCallTonePlayer.TONE_IN_CALL_QUALITY_NOTIFICATION) 526 .startTone(); 527 } 528 call.displayDiagnosticMessage(messageId, message); 529 } 530 531 /** 532 * Handles a request from a {@link CallDiagnosticService} to clear a previously displayed 533 * diagnostic message. 534 * @param callId the ID of the call to display the message for. 535 * @param messageId the message ID which was previous posted. 536 */ handleClearDiagnosticMessage(@onNull String callId, int messageId)537 private void handleClearDiagnosticMessage(@NonNull String callId, int messageId) { 538 Call call = mCallIdMapper.getCall(callId); 539 if (call == null) { 540 Log.w(this, "handleClearDiagnosticMessage: callId=%s; msg=%d; invalid call", 541 callId, messageId); 542 return; 543 } 544 Log.i(this, "handleClearDiagnosticMessage: callId=%s; msg=%d; invalid call", 545 callId, messageId); 546 call.clearDiagnosticMessage(messageId); 547 } 548 549 /** 550 * Handles a request from a {@link CallDiagnosticService} to send a device to device message. 551 * @param callId The ID of the call to send the D2D message for. 552 * @param message The message type. 553 * @param value The message value. 554 */ handleSendD2DMessage(@onNull String callId, @CallDiagnostics.MessageType int message, int value)555 private void handleSendD2DMessage(@NonNull String callId, 556 @CallDiagnostics.MessageType int message, int value) { 557 Call call = mCallIdMapper.getCall(callId); 558 if (call == null) { 559 Log.w(this, "handleSendD2DMessage: callId=%s; msg=%d/%d; invalid call", callId, 560 message, value); 561 return; 562 } 563 Log.i(this, "handleSendD2DMessage: callId=%s; msg=%d/%d", callId, message, value); 564 call.sendDeviceToDeviceMessage(message, value); 565 } 566 567 /** 568 * Handles a request from a {@link CallDiagnosticService} to override the disconnect message 569 * for a call. This is the response path from a previous call into the 570 * {@link CallDiagnosticService} via {@link CallDiagnostics#onCallDisconnected(ImsReasonInfo)}. 571 * @param callId The telecom call ID the disconnect override is pending for. 572 * @param message The new disconnect message, or {@code null} if no override. 573 */ handleOverrideDisconnectMessage(@onNull String callId, @Nullable CharSequence message)574 private void handleOverrideDisconnectMessage(@NonNull String callId, 575 @Nullable CharSequence message) { 576 Call call = mCallIdMapper.getCall(callId); 577 if (call == null) { 578 Log.w(this, "handleOverrideDisconnectMessage: callId=%s; msg=%s; invalid call", callId, 579 message); 580 return; 581 } 582 Log.i(this, "handleOverrideDisconnectMessage: callId=%s; msg=%s", callId, message); 583 call.handleOverrideDisconnectMessage(message); 584 } 585 586 /** 587 * Sends a single call to the bound {@link CallDiagnosticService}. 588 * @param call The call to send. 589 * @param callDiagnosticService The CDS to send it to. 590 */ sendCallToBoundService(@onNull Call call, @NonNull ICallDiagnosticService callDiagnosticService)591 private void sendCallToBoundService(@NonNull Call call, 592 @NonNull ICallDiagnosticService callDiagnosticService) { 593 try { 594 if (isConnected()) { 595 Log.w(this, "sendCallToBoundService: initializing %s", call.getId()); 596 callDiagnosticService.initializeDiagnosticCall(getParceledCall(call)); 597 } else { 598 Log.w(this, "sendCallToBoundService: not bound, skipping %s", call.getId()); 599 } 600 } catch (RemoteException e) { 601 Log.w(this, "sendCallToBoundService: callId=%s, exception=%s", call.getId(), e); 602 } 603 } 604 605 /** 606 * Removes a call from a bound {@link CallDiagnosticService}. 607 * @param call The call to remove. 608 * @param callDiagnosticService The CDS to remove it from. 609 */ removeCallFromBoundService(@onNull Call call, @NonNull ICallDiagnosticService callDiagnosticService)610 private void removeCallFromBoundService(@NonNull Call call, 611 @NonNull ICallDiagnosticService callDiagnosticService) { 612 try { 613 if (isConnected()) { 614 callDiagnosticService.removeDiagnosticCall(call.getId()); 615 } 616 } catch (RemoteException e) { 617 Log.w(this, "removeCallFromBoundService: callId=%s, exception=%s", call.getId(), e); 618 } 619 } 620 621 /** 622 * @return {@code true} if the call diagnostic service is bound/connected. 623 */ isConnected()624 public boolean isConnected() { 625 return mCallDiagnosticService != null; 626 } 627 628 /** 629 * Updates the Call diagnostic service with changes to a call. 630 * @param call The updated call. 631 */ updateCall(@onNull Call call)632 private void updateCall(@NonNull Call call) { 633 try { 634 if (isConnected()) { 635 mCallDiagnosticService.updateCall(getParceledCall(call)); 636 } 637 } catch (RemoteException e) { 638 Log.w(this, "updateCall: callId=%s, exception=%s", call.getId(), e); 639 } 640 } 641 642 /** 643 * Updates the call diagnostic service with a received bluetooth quality report. 644 * @param call The call. 645 * @param report The bluetooth call quality report. 646 */ handleBluetoothCallQualityReport(@onNull Call call, @NonNull BluetoothCallQualityReport report)647 private void handleBluetoothCallQualityReport(@NonNull Call call, 648 @NonNull BluetoothCallQualityReport report) { 649 try { 650 if (isConnected()) { 651 mCallDiagnosticService.receiveBluetoothCallQualityReport(report); 652 } 653 } catch (RemoteException e) { 654 Log.w(this, "handleBluetoothCallQualityReport: callId=%s, exception=%s", call.getId(), 655 e); 656 } 657 } 658 659 /** 660 * Informs a CallDiagnosticService of an incoming device to device message which was received 661 * via the carrier network. 662 * @param call the call the message was received via. 663 * @param messageType The message type. 664 * @param messageValue The message value. 665 */ handleReceivedDeviceToDeviceMessage(@onNull Call call, int messageType, int messageValue)666 private void handleReceivedDeviceToDeviceMessage(@NonNull Call call, int messageType, 667 int messageValue) { 668 try { 669 if (isConnected()) { 670 mCallDiagnosticService.receiveDeviceToDeviceMessage(call.getId(), messageType, 671 messageValue); 672 } 673 } catch (RemoteException e) { 674 Log.w(this, "handleReceivedDeviceToDeviceMessage: callId=%s, exception=%s", 675 call.getId(), e); 676 } 677 } 678 679 /** 680 * Handles a reported {@link CallQuality} report from a {@link android.telecom.Connection}. 681 * @param call The call the report originated from. 682 * @param callQualityReport The {@link CallQuality} report. 683 */ handleCallQualityReport(@onNull Call call, @NonNull CallQuality callQualityReport)684 private void handleCallQualityReport(@NonNull Call call, 685 @NonNull CallQuality callQualityReport) { 686 try { 687 if (isConnected()) { 688 mCallDiagnosticService.callQualityChanged(call.getId(), callQualityReport); 689 } 690 } catch (RemoteException e) { 691 Log.w(this, "handleCallQualityReport: callId=%s, exception=%s", 692 call.getId(), e); 693 } 694 } 695 696 /** 697 * Get a parcelled representation of a call for transport to the service. 698 * @param call The call. 699 * @return The parcelled call. 700 */ getParceledCall(@onNull Call call)701 private @NonNull ParcelableCall getParceledCall(@NonNull Call call) { 702 return ParcelableCallUtils.toParcelableCall( 703 call, 704 false /* includeVideoProvider */, 705 null /* phoneAcctRegistrar */, 706 false /* supportsExternalCalls */, 707 false /* includeRttCall */, 708 false /* isForSystemDialer */ 709 ); 710 } 711 712 /** 713 * Dumps the state of the {@link CallDiagnosticServiceController}. 714 * 715 * @param pw The {@code IndentingPrintWriter} to write the state to. 716 */ dump(IndentingPrintWriter pw)717 public void dump(IndentingPrintWriter pw) { 718 pw.print("activeCallDiagnosticService: "); 719 pw.println(getActiveCallDiagnosticService()); 720 pw.print("isConnected: "); 721 pw.println(isConnected()); 722 } 723 } 724