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