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