1/* 2 * Copyright 2014, 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 17package com.android.server.telecom; 18 19import android.content.ComponentName; 20import android.content.Context; 21import android.net.Uri; 22import android.os.Binder; 23import android.os.Bundle; 24import android.os.IBinder; 25import android.os.RemoteException; 26import android.os.UserHandle; 27import android.telecom.CallAudioState; 28import android.telecom.Connection; 29import android.telecom.ConnectionRequest; 30import android.telecom.ConnectionService; 31import android.telecom.DisconnectCause; 32import android.telecom.GatewayInfo; 33import android.telecom.ParcelableConference; 34import android.telecom.ParcelableConnection; 35import android.telecom.PhoneAccountHandle; 36import android.telecom.StatusHints; 37import android.telecom.TelecomManager; 38import android.telecom.VideoProfile; 39 40import com.android.internal.telecom.IConnectionService; 41import com.android.internal.telecom.IConnectionServiceAdapter; 42import com.android.internal.telecom.IVideoProvider; 43import com.android.internal.telecom.RemoteServiceCallback; 44import com.android.internal.util.Preconditions; 45 46import java.util.ArrayList; 47import java.util.Collections; 48import java.util.HashMap; 49import java.util.List; 50import java.util.Map; 51import java.util.Set; 52import java.util.concurrent.ConcurrentHashMap; 53 54/** 55 * Wrapper for {@link IConnectionService}s, handles binding to {@link IConnectionService} and keeps 56 * track of when the object can safely be unbound. Other classes should not use 57 * {@link IConnectionService} directly and instead should use this class to invoke methods of 58 * {@link IConnectionService}. 59 */ 60final class ConnectionServiceWrapper extends ServiceBinder { 61 62 private final class Adapter extends IConnectionServiceAdapter.Stub { 63 64 @Override 65 public void handleCreateConnectionComplete( 66 String callId, 67 ConnectionRequest request, 68 ParcelableConnection connection) { 69 long token = Binder.clearCallingIdentity(); 70 try { 71 synchronized (mLock) { 72 logIncoming("handleCreateConnectionComplete %s", callId); 73 if (mCallIdMapper.isValidCallId(callId)) { 74 ConnectionServiceWrapper.this 75 .handleCreateConnectionComplete(callId, request, connection); 76 } 77 } 78 } finally { 79 Binder.restoreCallingIdentity(token); 80 } 81 } 82 83 @Override 84 public void setActive(String callId) { 85 long token = Binder.clearCallingIdentity(); 86 try { 87 synchronized (mLock) { 88 logIncoming("setActive %s", callId); 89 if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper 90 .isValidConferenceId(callId)) { 91 Call call = mCallIdMapper.getCall(callId); 92 if (call != null) { 93 mCallsManager.markCallAsActive(call); 94 } else { 95 // Log.w(this, "setActive, unknown call id: %s", msg.obj); 96 } 97 } 98 } 99 } finally { 100 Binder.restoreCallingIdentity(token); 101 } 102 } 103 104 @Override 105 public void setRinging(String callId) { 106 long token = Binder.clearCallingIdentity(); 107 try { 108 synchronized (mLock) { 109 logIncoming("setRinging %s", callId); 110 if (mCallIdMapper.isValidCallId(callId)) { 111 Call call = mCallIdMapper.getCall(callId); 112 if (call != null) { 113 mCallsManager.markCallAsRinging(call); 114 } else { 115 // Log.w(this, "setRinging, unknown call id: %s", msg.obj); 116 } 117 } 118 } 119 } finally { 120 Binder.restoreCallingIdentity(token); 121 } 122 } 123 124 @Override 125 public void setVideoProvider(String callId, IVideoProvider videoProvider) { 126 long token = Binder.clearCallingIdentity(); 127 try { 128 synchronized (mLock) { 129 logIncoming("setVideoProvider %s", callId); 130 if (mCallIdMapper.isValidCallId(callId) 131 || mCallIdMapper.isValidConferenceId(callId)) { 132 Call call = mCallIdMapper.getCall(callId); 133 if (call != null) { 134 call.setVideoProvider(videoProvider); 135 } 136 } 137 } 138 } finally { 139 Binder.restoreCallingIdentity(token); 140 } 141 } 142 143 @Override 144 public void setDialing(String callId) { 145 long token = Binder.clearCallingIdentity(); 146 try { 147 synchronized (mLock) { 148 logIncoming("setDialing %s", callId); 149 if (mCallIdMapper.isValidCallId(callId)) { 150 Call call = mCallIdMapper.getCall(callId); 151 if (call != null) { 152 mCallsManager.markCallAsDialing(call); 153 } else { 154 // Log.w(this, "setDialing, unknown call id: %s", msg.obj); 155 } 156 } 157 } 158 } finally { 159 Binder.restoreCallingIdentity(token); 160 } 161 } 162 163 @Override 164 public void setDisconnected(String callId, DisconnectCause disconnectCause) { 165 long token = Binder.clearCallingIdentity(); 166 try { 167 synchronized (mLock) { 168 logIncoming("setDisconnected %s %s", callId, disconnectCause); 169 if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper 170 .isValidConferenceId(callId)) { 171 Call call = mCallIdMapper.getCall(callId); 172 Log.d(this, "disconnect call %s %s", disconnectCause, call); 173 if (call != null) { 174 mCallsManager.markCallAsDisconnected(call, disconnectCause); 175 } else { 176 // Log.w(this, "setDisconnected, unknown call id: %s", args.arg1); 177 } 178 } 179 } 180 } finally { 181 Binder.restoreCallingIdentity(token); 182 } 183 } 184 185 @Override 186 public void setOnHold(String callId) { 187 long token = Binder.clearCallingIdentity(); 188 try { 189 synchronized (mLock) { 190 logIncoming("setOnHold %s", callId); 191 if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper 192 .isValidConferenceId(callId)) { 193 Call call = mCallIdMapper.getCall(callId); 194 if (call != null) { 195 mCallsManager.markCallAsOnHold(call); 196 } else { 197 // Log.w(this, "setOnHold, unknown call id: %s", msg.obj); 198 } 199 } 200 } 201 } finally { 202 Binder.restoreCallingIdentity(token); 203 } 204 } 205 206 @Override 207 public void setRingbackRequested(String callId, boolean ringback) { 208 long token = Binder.clearCallingIdentity(); 209 try { 210 synchronized (mLock) { 211 logIncoming("setRingbackRequested %s %b", callId, ringback); 212 if (mCallIdMapper.isValidCallId(callId)) { 213 Call call = mCallIdMapper.getCall(callId); 214 if (call != null) { 215 call.setRingbackRequested(ringback); 216 } else { 217 // Log.w(this, "setRingback, unknown call id: %s", args.arg1); 218 } 219 } 220 } 221 } finally { 222 Binder.restoreCallingIdentity(token); 223 } 224 } 225 226 @Override 227 public void removeCall(String callId) { 228 long token = Binder.clearCallingIdentity(); 229 try { 230 synchronized (mLock) { 231 logIncoming("removeCall %s", callId); 232 if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper 233 .isValidConferenceId(callId)) { 234 Call call = mCallIdMapper.getCall(callId); 235 if (call != null) { 236 if (call.isAlive()) { 237 mCallsManager.markCallAsDisconnected( 238 call, new DisconnectCause(DisconnectCause.REMOTE)); 239 } else { 240 mCallsManager.markCallAsRemoved(call); 241 } 242 } 243 } 244 } 245 } finally { 246 Binder.restoreCallingIdentity(token); 247 } 248 } 249 250 @Override 251 public void setConnectionCapabilities(String callId, int connectionCapabilities) { 252 long token = Binder.clearCallingIdentity(); 253 try { 254 synchronized (mLock) { 255 logIncoming("setConnectionCapabilities %s %d", callId, connectionCapabilities); 256 if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper 257 .isValidConferenceId(callId)) { 258 Call call = mCallIdMapper.getCall(callId); 259 if (call != null) { 260 call.setConnectionCapabilities(connectionCapabilities); 261 } else { 262 // Log.w(ConnectionServiceWrapper.this, 263 // "setConnectionCapabilities, unknown call id: %s", msg.obj); 264 } 265 } 266 } 267 } finally { 268 Binder.restoreCallingIdentity(token); 269 } 270 } 271 272 @Override 273 public void setIsConferenced(String callId, String conferenceCallId) { 274 long token = Binder.clearCallingIdentity(); 275 try { 276 synchronized (mLock) { 277 logIncoming("setIsConferenced %s %s", callId, conferenceCallId); 278 Call childCall = mCallIdMapper.getCall(callId); 279 if (childCall != null) { 280 if (conferenceCallId == null) { 281 Log.d(this, "unsetting parent: %s", conferenceCallId); 282 childCall.setParentCall(null); 283 } else { 284 Call conferenceCall = mCallIdMapper.getCall(conferenceCallId); 285 childCall.setParentCall(conferenceCall); 286 } 287 } else { 288 // Log.w(this, "setIsConferenced, unknown call id: %s", args.arg1); 289 } 290 } 291 } finally { 292 Binder.restoreCallingIdentity(token); 293 } 294 } 295 296 @Override 297 public void setConferenceMergeFailed(String callId) { 298 long token = Binder.clearCallingIdentity(); 299 try { 300 synchronized (mLock) { 301 logIncoming("setConferenceMergeFailed %s", callId); 302 if (mCallIdMapper.isValidCallId(callId)) { 303 // TODO: we should move the UI for indication a merge failure here 304 // from CallNotifier.onSuppServiceFailed(). This way the InCallUI can 305 // deliver the message anyway that they want. b/20530631. 306 Call call = mCallIdMapper.getCall(callId); 307 if (call != null) { 308 // Just refresh the connection capabilities so that the UI 309 // is forced to reenable the merge button as the capability 310 // is still on the connection. Note when b/20530631 is fixed, we need 311 // to revisit this fix to remove this hacky way of unhiding the merge 312 // button (side effect of reprocessing the capabilities) and plumb 313 // the failure event all the way to InCallUI instead of stopping 314 // it here. That way we can also handle the UI of notifying that 315 // the merged has failed. 316 call.setConnectionCapabilities(call.getConnectionCapabilities(), true); 317 } else { 318 Log.w(this, "setConferenceMergeFailed, unknown call id: %s", callId); 319 } 320 } 321 322 } 323 } finally { 324 Binder.restoreCallingIdentity(token); 325 } 326 } 327 328 @Override 329 public void addConferenceCall(String callId, ParcelableConference parcelableConference) { 330 long token = Binder.clearCallingIdentity(); 331 try { 332 synchronized (mLock) { 333 if (mCallIdMapper.getCall(callId) != null) { 334 Log.w(this, "Attempting to add a conference call using an existing " + 335 "call id %s", callId); 336 return; 337 } 338 339 // Make sure that there's at least one valid call. For remote connections 340 // we'll get a add conference msg from both the remote connection service 341 // and from the real connection service. 342 boolean hasValidCalls = false; 343 for (String connId : parcelableConference.getConnectionIds()) { 344 if (mCallIdMapper.getCall(connId) != null) { 345 hasValidCalls = true; 346 } 347 } 348 // But don't bail out if the connection count is 0, because that is a valid 349 // IMS conference state. 350 if (!hasValidCalls && parcelableConference.getConnectionIds().size() > 0) { 351 Log.d(this, "Attempting to add a conference with no valid calls"); 352 return; 353 } 354 355 // need to create a new Call 356 PhoneAccountHandle phAcc = null; 357 if (parcelableConference != null && 358 parcelableConference.getPhoneAccount() != null) { 359 phAcc = parcelableConference.getPhoneAccount(); 360 } 361 Call conferenceCall = mCallsManager.createConferenceCall( 362 phAcc, parcelableConference); 363 mCallIdMapper.addCall(conferenceCall, callId); 364 conferenceCall.setConnectionService(ConnectionServiceWrapper.this); 365 366 Log.d(this, "adding children to conference %s phAcc %s", 367 parcelableConference.getConnectionIds(), phAcc); 368 for (String connId : parcelableConference.getConnectionIds()) { 369 Call childCall = mCallIdMapper.getCall(connId); 370 Log.d(this, "found child: %s", connId); 371 if (childCall != null) { 372 childCall.setParentCall(conferenceCall); 373 } 374 } 375 } 376 } finally { 377 Binder.restoreCallingIdentity(token); 378 } 379 } 380 381 @Override 382 public void onPostDialWait(String callId, String remaining) throws RemoteException { 383 long token = Binder.clearCallingIdentity(); 384 try { 385 synchronized (mLock) { 386 logIncoming("onPostDialWait %s %s", callId, remaining); 387 if (mCallIdMapper.isValidCallId(callId)) { 388 Call call = mCallIdMapper.getCall(callId); 389 if (call != null) { 390 call.onPostDialWait(remaining); 391 } else { 392 // Log.w(this, "onPostDialWait, unknown call id: %s", args.arg1); 393 } 394 } 395 } 396 } finally { 397 Binder.restoreCallingIdentity(token); 398 } 399 } 400 401 @Override 402 public void onPostDialChar(String callId, char nextChar) throws RemoteException { 403 long token = Binder.clearCallingIdentity(); 404 try { 405 synchronized (mLock) { 406 logIncoming("onPostDialChar %s %s", callId, nextChar); 407 if (mCallIdMapper.isValidCallId(callId)) { 408 Call call = mCallIdMapper.getCall(callId); 409 if (call != null) { 410 call.onPostDialChar(nextChar); 411 } else { 412 // Log.w(this, "onPostDialChar, unknown call id: %s", args.arg1); 413 } 414 } 415 } 416 } finally { 417 Binder.restoreCallingIdentity(token); 418 } 419 } 420 421 @Override 422 public void queryRemoteConnectionServices(RemoteServiceCallback callback) { 423 long token = Binder.clearCallingIdentity(); 424 try { 425 synchronized (mLock) { 426 logIncoming("queryRemoteConnectionServices %s", callback); 427 ConnectionServiceWrapper.this.queryRemoteConnectionServices(callback); 428 } 429 } finally { 430 Binder.restoreCallingIdentity(token); 431 } 432 } 433 434 @Override 435 public void setVideoState(String callId, int videoState) { 436 long token = Binder.clearCallingIdentity(); 437 try { 438 synchronized (mLock) { 439 logIncoming("setVideoState %s %d", callId, videoState); 440 if (mCallIdMapper.isValidCallId(callId) 441 || mCallIdMapper.isValidConferenceId(callId)) { 442 Call call = mCallIdMapper.getCall(callId); 443 if (call != null) { 444 call.setVideoState(videoState); 445 } 446 } 447 } 448 } finally { 449 Binder.restoreCallingIdentity(token); 450 } 451 } 452 453 @Override 454 public void setIsVoipAudioMode(String callId, boolean isVoip) { 455 long token = Binder.clearCallingIdentity(); 456 try { 457 synchronized (mLock) { 458 logIncoming("setIsVoipAudioMode %s %b", callId, isVoip); 459 if (mCallIdMapper.isValidCallId(callId)) { 460 Call call = mCallIdMapper.getCall(callId); 461 if (call != null) { 462 call.setIsVoipAudioMode(isVoip); 463 } 464 } 465 } 466 } finally { 467 Binder.restoreCallingIdentity(token); 468 } 469 } 470 471 @Override 472 public void setStatusHints(String callId, StatusHints statusHints) { 473 long token = Binder.clearCallingIdentity(); 474 try { 475 synchronized (mLock) { 476 logIncoming("setStatusHints %s %s", callId, statusHints); 477 if (mCallIdMapper.isValidCallId(callId) 478 || mCallIdMapper.isValidConferenceId(callId)) { 479 Call call = mCallIdMapper.getCall(callId); 480 if (call != null) { 481 call.setStatusHints(statusHints); 482 } 483 } 484 } 485 } finally { 486 Binder.restoreCallingIdentity(token); 487 } 488 } 489 490 @Override 491 public void setExtras(String callId, Bundle extras) { 492 long token = Binder.clearCallingIdentity(); 493 try { 494 synchronized(mLock) { 495 logIncoming("setExtras %s %s", callId, extras); 496 if (mCallIdMapper.isValidCallId(callId) 497 || mCallIdMapper.isValidConferenceId(callId)) { 498 Call call = mCallIdMapper.getCall(callId); 499 if (call != null) { 500 call.setExtras(extras); 501 } 502 } 503 } 504 } finally { 505 Binder.restoreCallingIdentity(token); 506 } 507 } 508 509 @Override 510 public void setAddress(String callId, Uri address, int presentation) { 511 long token = Binder.clearCallingIdentity(); 512 try { 513 synchronized (mLock) { 514 logIncoming("setAddress %s %s %d", callId, address, presentation); 515 if (mCallIdMapper.isValidCallId(callId)) { 516 Call call = mCallIdMapper.getCall(callId); 517 if (call != null) { 518 call.setHandle(address, presentation); 519 } 520 } 521 } 522 } finally { 523 Binder.restoreCallingIdentity(token); 524 } 525 } 526 527 @Override 528 public void setCallerDisplayName( 529 String callId, String callerDisplayName, int presentation) { 530 long token = Binder.clearCallingIdentity(); 531 try { 532 synchronized (mLock) { 533 logIncoming("setCallerDisplayName %s %s %d", callId, callerDisplayName, 534 presentation); 535 if (mCallIdMapper.isValidCallId(callId)) { 536 Call call = mCallIdMapper.getCall(callId); 537 if (call != null) { 538 call.setCallerDisplayName(callerDisplayName, presentation); 539 } 540 } 541 } 542 } finally { 543 Binder.restoreCallingIdentity(token); 544 } 545 } 546 547 @Override 548 public void setConferenceableConnections( 549 String callId, List<String> conferenceableCallIds) { 550 long token = Binder.clearCallingIdentity(); 551 try { 552 synchronized (mLock) { 553 logIncoming("setConferenceableConnections %s %s", callId, 554 conferenceableCallIds); 555 if (mCallIdMapper.isValidCallId(callId) || 556 mCallIdMapper.isValidConferenceId(callId)) { 557 Call call = mCallIdMapper.getCall(callId); 558 if (call != null) { 559 List<Call> conferenceableCalls = 560 new ArrayList<>(conferenceableCallIds.size()); 561 for (String otherId : conferenceableCallIds) { 562 Call otherCall = mCallIdMapper.getCall(otherId); 563 if (otherCall != null && otherCall != call) { 564 conferenceableCalls.add(otherCall); 565 } 566 } 567 call.setConferenceableCalls(conferenceableCalls); 568 } 569 } 570 } 571 } finally { 572 Binder.restoreCallingIdentity(token); 573 } 574 } 575 576 @Override 577 public void addExistingConnection(String callId, ParcelableConnection connection) { 578 long token = Binder.clearCallingIdentity(); 579 try { 580 synchronized (mLock) { 581 logIncoming("addExistingConnection %s %s", callId, connection); 582 Call existingCall = mCallsManager 583 .createCallForExistingConnection(callId, connection); 584 mCallIdMapper.addCall(existingCall, callId); 585 existingCall.setConnectionService(ConnectionServiceWrapper.this); 586 } 587 } finally { 588 Binder.restoreCallingIdentity(token); 589 } 590 } 591 } 592 593 private final Adapter mAdapter = new Adapter(); 594 private final CallIdMapper mCallIdMapper = new CallIdMapper("ConnectionService"); 595 private final Map<String, CreateConnectionResponse> mPendingResponses = new HashMap<>(); 596 597 private Binder2 mBinder = new Binder2(); 598 private IConnectionService mServiceInterface; 599 private final ConnectionServiceRepository mConnectionServiceRepository; 600 private final PhoneAccountRegistrar mPhoneAccountRegistrar; 601 private final CallsManager mCallsManager; 602 603 /** 604 * Creates a connection service. 605 * 606 * @param componentName The component name of the service with which to bind. 607 * @param connectionServiceRepository Connection service repository. 608 * @param phoneAccountRegistrar Phone account registrar 609 * @param callsManager Calls manager 610 * @param context The context. 611 * @param userHandle The {@link UserHandle} to use when binding. 612 */ 613 ConnectionServiceWrapper( 614 ComponentName componentName, 615 ConnectionServiceRepository connectionServiceRepository, 616 PhoneAccountRegistrar phoneAccountRegistrar, 617 CallsManager callsManager, 618 Context context, 619 TelecomSystem.SyncRoot lock, 620 UserHandle userHandle) { 621 super(ConnectionService.SERVICE_INTERFACE, componentName, context, lock, userHandle); 622 mConnectionServiceRepository = connectionServiceRepository; 623 phoneAccountRegistrar.addListener(new PhoneAccountRegistrar.Listener() { 624 // TODO -- Upon changes to PhoneAccountRegistrar, need to re-wire connections 625 // To do this, we must proxy remote ConnectionService objects 626 }); 627 mPhoneAccountRegistrar = phoneAccountRegistrar; 628 mCallsManager = callsManager; 629 } 630 631 /** See {@link IConnectionService#addConnectionServiceAdapter}. */ 632 private void addConnectionServiceAdapter(IConnectionServiceAdapter adapter) { 633 if (isServiceValid("addConnectionServiceAdapter")) { 634 try { 635 logOutgoing("addConnectionServiceAdapter %s", adapter); 636 mServiceInterface.addConnectionServiceAdapter(adapter); 637 } catch (RemoteException e) { 638 } 639 } 640 } 641 642 /** 643 * Creates a new connection for a new outgoing call or to attach to an existing incoming call. 644 */ 645 void createConnection(final Call call, final CreateConnectionResponse response) { 646 Log.d(this, "createConnection(%s) via %s.", call, getComponentName()); 647 BindCallback callback = new BindCallback() { 648 @Override 649 public void onSuccess() { 650 String callId = mCallIdMapper.getCallId(call); 651 mPendingResponses.put(callId, response); 652 653 GatewayInfo gatewayInfo = call.getGatewayInfo(); 654 Bundle extras = call.getIntentExtras(); 655 if (gatewayInfo != null && gatewayInfo.getGatewayProviderPackageName() != null && 656 gatewayInfo.getOriginalAddress() != null) { 657 extras = (Bundle) extras.clone(); 658 extras.putString( 659 TelecomManager.GATEWAY_PROVIDER_PACKAGE, 660 gatewayInfo.getGatewayProviderPackageName()); 661 extras.putParcelable( 662 TelecomManager.GATEWAY_ORIGINAL_ADDRESS, 663 gatewayInfo.getOriginalAddress()); 664 } 665 666 Log.event(call, Log.Events.START_CONNECTION, Log.piiHandle(call.getHandle())); 667 try { 668 mServiceInterface.createConnection( 669 call.getConnectionManagerPhoneAccount(), 670 callId, 671 new ConnectionRequest( 672 call.getTargetPhoneAccount(), 673 call.getHandle(), 674 extras, 675 call.getVideoState()), 676 call.isIncoming(), 677 call.isUnknown()); 678 } catch (RemoteException e) { 679 Log.e(this, e, "Failure to createConnection -- %s", getComponentName()); 680 mPendingResponses.remove(callId).handleCreateConnectionFailure( 681 new DisconnectCause(DisconnectCause.ERROR, e.toString())); 682 } 683 } 684 685 @Override 686 public void onFailure() { 687 Log.e(this, new Exception(), "Failure to call %s", getComponentName()); 688 response.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.ERROR)); 689 } 690 }; 691 692 mBinder.bind(callback, call); 693 } 694 695 /** @see IConnectionService#abort(String) */ 696 void abort(Call call) { 697 // Clear out any pending outgoing call data 698 final String callId = mCallIdMapper.getCallId(call); 699 700 // If still bound, tell the connection service to abort. 701 if (callId != null && isServiceValid("abort")) { 702 try { 703 logOutgoing("abort %s", callId); 704 mServiceInterface.abort(callId); 705 } catch (RemoteException e) { 706 } 707 } 708 709 removeCall(call, new DisconnectCause(DisconnectCause.LOCAL)); 710 } 711 712 /** @see IConnectionService#hold(String) */ 713 void hold(Call call) { 714 final String callId = mCallIdMapper.getCallId(call); 715 if (callId != null && isServiceValid("hold")) { 716 try { 717 logOutgoing("hold %s", callId); 718 mServiceInterface.hold(callId); 719 } catch (RemoteException e) { 720 } 721 } 722 } 723 724 /** @see IConnectionService#unhold(String) */ 725 void unhold(Call call) { 726 final String callId = mCallIdMapper.getCallId(call); 727 if (callId != null && isServiceValid("unhold")) { 728 try { 729 logOutgoing("unhold %s", callId); 730 mServiceInterface.unhold(callId); 731 } catch (RemoteException e) { 732 } 733 } 734 } 735 736 /** @see IConnectionService#onCallAudioStateChanged(String,CallAudioState) */ 737 void onCallAudioStateChanged(Call activeCall, CallAudioState audioState) { 738 final String callId = mCallIdMapper.getCallId(activeCall); 739 if (callId != null && isServiceValid("onCallAudioStateChanged")) { 740 try { 741 logOutgoing("onCallAudioStateChanged %s %s", callId, audioState); 742 mServiceInterface.onCallAudioStateChanged(callId, audioState); 743 } catch (RemoteException e) { 744 } 745 } 746 } 747 748 /** @see IConnectionService#disconnect(String) */ 749 void disconnect(Call call) { 750 final String callId = mCallIdMapper.getCallId(call); 751 if (callId != null && isServiceValid("disconnect")) { 752 try { 753 logOutgoing("disconnect %s", callId); 754 mServiceInterface.disconnect(callId); 755 } catch (RemoteException e) { 756 } 757 } 758 } 759 760 /** @see IConnectionService#answer(String) */ 761 void answer(Call call, int videoState) { 762 final String callId = mCallIdMapper.getCallId(call); 763 if (callId != null && isServiceValid("answer")) { 764 try { 765 logOutgoing("answer %s %d", callId, videoState); 766 if (VideoProfile.isAudioOnly(videoState)) { 767 mServiceInterface.answer(callId); 768 } else { 769 mServiceInterface.answerVideo(callId, videoState); 770 } 771 } catch (RemoteException e) { 772 } 773 } 774 } 775 776 /** @see IConnectionService#reject(String) */ 777 void reject(Call call) { 778 final String callId = mCallIdMapper.getCallId(call); 779 if (callId != null && isServiceValid("reject")) { 780 try { 781 logOutgoing("reject %s", callId); 782 mServiceInterface.reject(callId); 783 } catch (RemoteException e) { 784 } 785 } 786 } 787 788 /** @see IConnectionService#playDtmfTone(String,char) */ 789 void playDtmfTone(Call call, char digit) { 790 final String callId = mCallIdMapper.getCallId(call); 791 if (callId != null && isServiceValid("playDtmfTone")) { 792 try { 793 logOutgoing("playDtmfTone %s %c", callId, digit); 794 mServiceInterface.playDtmfTone(callId, digit); 795 } catch (RemoteException e) { 796 } 797 } 798 } 799 800 /** @see IConnectionService#stopDtmfTone(String) */ 801 void stopDtmfTone(Call call) { 802 final String callId = mCallIdMapper.getCallId(call); 803 if (callId != null && isServiceValid("stopDtmfTone")) { 804 try { 805 logOutgoing("stopDtmfTone %s",callId); 806 mServiceInterface.stopDtmfTone(callId); 807 } catch (RemoteException e) { 808 } 809 } 810 } 811 812 void addCall(Call call) { 813 if (mCallIdMapper.getCallId(call) == null) { 814 mCallIdMapper.addCall(call); 815 } 816 } 817 818 /** 819 * Associates newCall with this connection service by replacing callToReplace. 820 */ 821 void replaceCall(Call newCall, Call callToReplace) { 822 Preconditions.checkState(callToReplace.getConnectionService() == this); 823 mCallIdMapper.replaceCall(newCall, callToReplace); 824 } 825 826 void removeCall(Call call) { 827 removeCall(call, new DisconnectCause(DisconnectCause.ERROR)); 828 } 829 830 void removeCall(String callId, DisconnectCause disconnectCause) { 831 CreateConnectionResponse response = mPendingResponses.remove(callId); 832 if (response != null) { 833 response.handleCreateConnectionFailure(disconnectCause); 834 } 835 836 mCallIdMapper.removeCall(callId); 837 } 838 839 void removeCall(Call call, DisconnectCause disconnectCause) { 840 CreateConnectionResponse response = mPendingResponses.remove(mCallIdMapper.getCallId(call)); 841 if (response != null) { 842 response.handleCreateConnectionFailure(disconnectCause); 843 } 844 845 mCallIdMapper.removeCall(call); 846 } 847 848 void onPostDialContinue(Call call, boolean proceed) { 849 final String callId = mCallIdMapper.getCallId(call); 850 if (callId != null && isServiceValid("onPostDialContinue")) { 851 try { 852 logOutgoing("onPostDialContinue %s %b", callId, proceed); 853 mServiceInterface.onPostDialContinue(callId, proceed); 854 } catch (RemoteException ignored) { 855 } 856 } 857 } 858 859 void conference(final Call call, Call otherCall) { 860 final String callId = mCallIdMapper.getCallId(call); 861 final String otherCallId = mCallIdMapper.getCallId(otherCall); 862 if (callId != null && otherCallId != null && isServiceValid("conference")) { 863 try { 864 logOutgoing("conference %s %s", callId, otherCallId); 865 mServiceInterface.conference(callId, otherCallId); 866 } catch (RemoteException ignored) { 867 } 868 } 869 } 870 871 void splitFromConference(Call call) { 872 final String callId = mCallIdMapper.getCallId(call); 873 if (callId != null && isServiceValid("splitFromConference")) { 874 try { 875 logOutgoing("splitFromConference %s", callId); 876 mServiceInterface.splitFromConference(callId); 877 } catch (RemoteException ignored) { 878 } 879 } 880 } 881 882 void mergeConference(Call call) { 883 final String callId = mCallIdMapper.getCallId(call); 884 if (callId != null && isServiceValid("mergeConference")) { 885 try { 886 logOutgoing("mergeConference %s", callId); 887 mServiceInterface.mergeConference(callId); 888 } catch (RemoteException ignored) { 889 } 890 } 891 } 892 893 void swapConference(Call call) { 894 final String callId = mCallIdMapper.getCallId(call); 895 if (callId != null && isServiceValid("swapConference")) { 896 try { 897 logOutgoing("swapConference %s", callId); 898 mServiceInterface.swapConference(callId); 899 } catch (RemoteException ignored) { 900 } 901 } 902 } 903 904 /** {@inheritDoc} */ 905 @Override 906 protected void setServiceInterface(IBinder binder) { 907 if (binder == null) { 908 // We have lost our service connection. Notify the world that this service is done. 909 // We must notify the adapter before CallsManager. The adapter will force any pending 910 // outgoing calls to try the next service. This needs to happen before CallsManager 911 // tries to clean up any calls still associated with this service. 912 handleConnectionServiceDeath(); 913 mCallsManager.handleConnectionServiceDeath(this); 914 mServiceInterface = null; 915 } else { 916 mServiceInterface = IConnectionService.Stub.asInterface(binder); 917 addConnectionServiceAdapter(mAdapter); 918 } 919 } 920 921 private void handleCreateConnectionComplete( 922 String callId, 923 ConnectionRequest request, 924 ParcelableConnection connection) { 925 // TODO: Note we are not using parameter "request", which is a side effect of our tacit 926 // assumption that we have at most one outgoing connection attempt per ConnectionService. 927 // This may not continue to be the case. 928 if (connection.getState() == Connection.STATE_DISCONNECTED) { 929 // A connection that begins in the DISCONNECTED state is an indication of 930 // failure to connect; we handle all failures uniformly 931 removeCall(callId, connection.getDisconnectCause()); 932 } else { 933 // Successful connection 934 if (mPendingResponses.containsKey(callId)) { 935 mPendingResponses.remove(callId) 936 .handleCreateConnectionSuccess(mCallIdMapper, connection); 937 } 938 } 939 } 940 941 /** 942 * Called when the associated connection service dies. 943 */ 944 private void handleConnectionServiceDeath() { 945 if (!mPendingResponses.isEmpty()) { 946 CreateConnectionResponse[] responses = mPendingResponses.values().toArray( 947 new CreateConnectionResponse[mPendingResponses.values().size()]); 948 mPendingResponses.clear(); 949 for (int i = 0; i < responses.length; i++) { 950 responses[i].handleCreateConnectionFailure( 951 new DisconnectCause(DisconnectCause.ERROR)); 952 } 953 } 954 mCallIdMapper.clear(); 955 } 956 957 private void logIncoming(String msg, Object... params) { 958 Log.d(this, "ConnectionService -> Telecom: " + msg, params); 959 } 960 961 private void logOutgoing(String msg, Object... params) { 962 Log.d(this, "Telecom -> ConnectionService: " + msg, params); 963 } 964 965 private void queryRemoteConnectionServices(final RemoteServiceCallback callback) { 966 // Only give remote connection services to this connection service if it is listed as 967 // the connection manager. 968 PhoneAccountHandle simCallManager = mPhoneAccountRegistrar.getSimCallManager(); 969 Log.d(this, "queryRemoteConnectionServices finds simCallManager = %s", simCallManager); 970 if (simCallManager == null || 971 !simCallManager.getComponentName().equals(getComponentName())) { 972 noRemoteServices(callback); 973 return; 974 } 975 976 // Make a list of ConnectionServices that are listed as being associated with SIM accounts 977 final Set<ConnectionServiceWrapper> simServices = Collections.newSetFromMap( 978 new ConcurrentHashMap<ConnectionServiceWrapper, Boolean>(8, 0.9f, 1)); 979 for (PhoneAccountHandle handle : mPhoneAccountRegistrar.getSimPhoneAccounts()) { 980 ConnectionServiceWrapper service = mConnectionServiceRepository.getService( 981 handle.getComponentName(), handle.getUserHandle()); 982 if (service != null) { 983 simServices.add(service); 984 } 985 } 986 987 final List<ComponentName> simServiceComponentNames = new ArrayList<>(); 988 final List<IBinder> simServiceBinders = new ArrayList<>(); 989 990 Log.v(this, "queryRemoteConnectionServices, simServices = %s", simServices); 991 992 for (ConnectionServiceWrapper simService : simServices) { 993 if (simService == this) { 994 // Only happens in the unlikely case that a SIM service is also a SIM call manager 995 continue; 996 } 997 998 final ConnectionServiceWrapper currentSimService = simService; 999 1000 currentSimService.mBinder.bind(new BindCallback() { 1001 @Override 1002 public void onSuccess() { 1003 Log.d(this, "Adding simService %s", currentSimService.getComponentName()); 1004 simServiceComponentNames.add(currentSimService.getComponentName()); 1005 simServiceBinders.add(currentSimService.mServiceInterface.asBinder()); 1006 maybeComplete(); 1007 } 1008 1009 @Override 1010 public void onFailure() { 1011 Log.d(this, "Failed simService %s", currentSimService.getComponentName()); 1012 // We know maybeComplete() will always be a no-op from now on, so go ahead and 1013 // signal failure of the entire request 1014 noRemoteServices(callback); 1015 } 1016 1017 private void maybeComplete() { 1018 if (simServiceComponentNames.size() == simServices.size()) { 1019 setRemoteServices(callback, simServiceComponentNames, simServiceBinders); 1020 } 1021 } 1022 }, null); 1023 } 1024 } 1025 1026 private void setRemoteServices( 1027 RemoteServiceCallback callback, 1028 List<ComponentName> componentNames, 1029 List<IBinder> binders) { 1030 try { 1031 callback.onResult(componentNames, binders); 1032 } catch (RemoteException e) { 1033 Log.e(this, e, "Contacting ConnectionService %s", 1034 ConnectionServiceWrapper.this.getComponentName()); 1035 } 1036 } 1037 1038 private void noRemoteServices(RemoteServiceCallback callback) { 1039 setRemoteServices(callback, Collections.EMPTY_LIST, Collections.EMPTY_LIST); 1040 } 1041} 1042