1 /* 2 * Copyright (C) 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 17 package android.telecom; 18 19 import android.net.Uri; 20 import android.os.Bundle; 21 import android.os.IBinder; 22 import android.os.IBinder.DeathRecipient; 23 import android.os.RemoteException; 24 import android.telecom.Logging.Session; 25 26 import com.android.internal.telecom.IConnectionService; 27 import com.android.internal.telecom.IConnectionServiceAdapter; 28 import com.android.internal.telecom.IVideoProvider; 29 import com.android.internal.telecom.RemoteServiceCallback; 30 31 import java.util.ArrayList; 32 import java.util.HashMap; 33 import java.util.HashSet; 34 import java.util.List; 35 import java.util.Map; 36 import java.util.Set; 37 import java.util.UUID; 38 39 /** 40 * Remote connection service which other connection services can use to place calls on their behalf. 41 * 42 * @hide 43 */ 44 final class RemoteConnectionService { 45 46 // Note: Casting null to avoid ambiguous constructor reference. 47 private static final RemoteConnection NULL_CONNECTION = 48 new RemoteConnection("NULL", null, (ConnectionRequest) null); 49 50 private static final RemoteConference NULL_CONFERENCE = 51 new RemoteConference("NULL", null); 52 53 private final IConnectionServiceAdapter mServantDelegate = new IConnectionServiceAdapter() { 54 @Override 55 public void handleCreateConnectionComplete( 56 String id, 57 ConnectionRequest request, 58 ParcelableConnection parcel, 59 Session.Info info) { 60 RemoteConnection connection = 61 findConnectionForAction(id, "handleCreateConnectionSuccessful"); 62 if (connection != NULL_CONNECTION && mPendingConnections.contains(connection)) { 63 mPendingConnections.remove(connection); 64 // Unconditionally initialize the connection ... 65 connection.setConnectionCapabilities(parcel.getConnectionCapabilities()); 66 connection.setConnectionProperties(parcel.getConnectionProperties()); 67 if (parcel.getHandle() != null 68 || parcel.getState() != Connection.STATE_DISCONNECTED) { 69 connection.setAddress(parcel.getHandle(), parcel.getHandlePresentation()); 70 } 71 if (parcel.getCallerDisplayName() != null 72 || parcel.getState() != Connection.STATE_DISCONNECTED) { 73 connection.setCallerDisplayName( 74 parcel.getCallerDisplayName(), 75 parcel.getCallerDisplayNamePresentation()); 76 } 77 // Set state after handle so that the client can identify the connection. 78 if (parcel.getState() == Connection.STATE_DISCONNECTED) { 79 connection.setDisconnected(parcel.getDisconnectCause()); 80 } else { 81 connection.setState(parcel.getState()); 82 } 83 List<RemoteConnection> conferenceable = new ArrayList<>(); 84 for (String confId : parcel.getConferenceableConnectionIds()) { 85 if (mConnectionById.containsKey(confId)) { 86 conferenceable.add(mConnectionById.get(confId)); 87 } 88 } 89 connection.setConferenceableConnections(conferenceable); 90 connection.setVideoState(parcel.getVideoState()); 91 if (connection.getState() == Connection.STATE_DISCONNECTED) { 92 // ... then, if it was created in a disconnected state, that indicates 93 // failure on the providing end, so immediately mark it destroyed 94 connection.setDestroyed(); 95 } 96 connection.setStatusHints(parcel.getStatusHints()); 97 connection.setIsVoipAudioMode(parcel.getIsVoipAudioMode()); 98 connection.setRingbackRequested(parcel.isRingbackRequested()); 99 connection.putExtras(parcel.getExtras()); 100 } 101 } 102 103 @Override 104 public void handleCreateConferenceComplete( 105 String id, 106 ConnectionRequest request, 107 ParcelableConference parcel, 108 Session.Info info) { 109 } 110 111 @Override 112 public void setActive(String callId, Session.Info sessionInfo) { 113 if (mConnectionById.containsKey(callId)) { 114 findConnectionForAction(callId, "setActive") 115 .setState(Connection.STATE_ACTIVE); 116 } else { 117 findConferenceForAction(callId, "setActive") 118 .setState(Connection.STATE_ACTIVE); 119 } 120 } 121 122 @Override 123 public void setRinging(String callId, Session.Info sessionInfo) { 124 findConnectionForAction(callId, "setRinging") 125 .setState(Connection.STATE_RINGING); 126 } 127 128 @Override 129 public void setDialing(String callId, Session.Info sessionInfo) { 130 findConnectionForAction(callId, "setDialing") 131 .setState(Connection.STATE_DIALING); 132 } 133 134 @Override 135 public void setPulling(String callId, Session.Info sessionInfo) { 136 findConnectionForAction(callId, "setPulling") 137 .setState(Connection.STATE_PULLING_CALL); 138 } 139 140 @Override 141 public void setDisconnected(String callId, DisconnectCause disconnectCause, 142 Session.Info sessionInfo) { 143 if (mConnectionById.containsKey(callId)) { 144 findConnectionForAction(callId, "setDisconnected") 145 .setDisconnected(disconnectCause); 146 } else { 147 findConferenceForAction(callId, "setDisconnected") 148 .setDisconnected(disconnectCause); 149 } 150 } 151 152 @Override 153 public void setOnHold(String callId, Session.Info sessionInfo) { 154 if (mConnectionById.containsKey(callId)) { 155 findConnectionForAction(callId, "setOnHold") 156 .setState(Connection.STATE_HOLDING); 157 } else { 158 findConferenceForAction(callId, "setOnHold") 159 .setState(Connection.STATE_HOLDING); 160 } 161 } 162 163 @Override 164 public void setRingbackRequested(String callId, boolean ringing, Session.Info sessionInfo) { 165 findConnectionForAction(callId, "setRingbackRequested") 166 .setRingbackRequested(ringing); 167 } 168 169 @Override 170 public void setConnectionCapabilities(String callId, int connectionCapabilities, 171 Session.Info sessionInfo) { 172 if (mConnectionById.containsKey(callId)) { 173 findConnectionForAction(callId, "setConnectionCapabilities") 174 .setConnectionCapabilities(connectionCapabilities); 175 } else { 176 findConferenceForAction(callId, "setConnectionCapabilities") 177 .setConnectionCapabilities(connectionCapabilities); 178 } 179 } 180 181 @Override 182 public void setConnectionProperties(String callId, int connectionProperties, 183 Session.Info sessionInfo) { 184 if (mConnectionById.containsKey(callId)) { 185 findConnectionForAction(callId, "setConnectionProperties") 186 .setConnectionProperties(connectionProperties); 187 } else { 188 findConferenceForAction(callId, "setConnectionProperties") 189 .setConnectionProperties(connectionProperties); 190 } 191 } 192 193 @Override 194 public void setIsConferenced(String callId, String conferenceCallId, 195 Session.Info sessionInfo) { 196 // Note: callId should not be null; conferenceCallId may be null 197 RemoteConnection connection = 198 findConnectionForAction(callId, "setIsConferenced"); 199 if (connection != NULL_CONNECTION) { 200 if (conferenceCallId == null) { 201 // 'connection' is being split from its conference 202 if (connection.getConference() != null) { 203 connection.getConference().removeConnection(connection); 204 } 205 } else { 206 RemoteConference conference = 207 findConferenceForAction(conferenceCallId, "setIsConferenced"); 208 if (conference != NULL_CONFERENCE) { 209 conference.addConnection(connection); 210 } 211 } 212 } 213 } 214 215 @Override 216 public void setConferenceMergeFailed(String callId, Session.Info sessionInfo) { 217 // Nothing to do here. 218 // The event has already been handled and there is no state to update 219 // in the underlying connection or conference objects 220 } 221 222 @Override 223 public void onPhoneAccountChanged(String callId, PhoneAccountHandle pHandle, 224 Session.Info sessionInfo) { 225 } 226 227 @Override 228 public void onConnectionServiceFocusReleased(Session.Info sessionInfo) {} 229 230 @Override 231 public void addConferenceCall( 232 final String callId, ParcelableConference parcel, Session.Info sessionInfo) { 233 RemoteConference conference = new RemoteConference(callId, 234 mOutgoingConnectionServiceRpc); 235 236 for (String id : parcel.getConnectionIds()) { 237 RemoteConnection c = mConnectionById.get(id); 238 if (c != null) { 239 conference.addConnection(c); 240 } 241 } 242 if (conference.getConnections().size() == 0) { 243 // A conference was created, but none of its connections are ones that have been 244 // created by, and therefore being tracked by, this remote connection service. It 245 // is of no interest to us. 246 Log.d(this, "addConferenceCall - skipping"); 247 return; 248 } 249 250 conference.setState(parcel.getState()); 251 conference.setConnectionCapabilities(parcel.getConnectionCapabilities()); 252 conference.setConnectionProperties(parcel.getConnectionProperties()); 253 conference.putExtras(parcel.getExtras()); 254 mConferenceById.put(callId, conference); 255 256 // Stash the original connection ID as it exists in the source ConnectionService. 257 // Telecom will use this to avoid adding duplicates later. 258 // See comments on Connection.EXTRA_ORIGINAL_CONNECTION_ID for more information. 259 Bundle newExtras = new Bundle(); 260 newExtras.putString(Connection.EXTRA_ORIGINAL_CONNECTION_ID, callId); 261 // Track the fact this request was relayed through the remote connection service. 262 newExtras.putParcelable(Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE, 263 parcel.getPhoneAccount()); 264 conference.putExtras(newExtras); 265 266 conference.registerCallback(new RemoteConference.Callback() { 267 @Override 268 public void onDestroyed(RemoteConference c) { 269 mConferenceById.remove(callId); 270 maybeDisconnectAdapter(); 271 } 272 }); 273 274 mOurConnectionServiceImpl.addRemoteConference(conference); 275 } 276 277 @Override 278 public void removeCall(String callId, Session.Info sessionInfo) { 279 if (mConnectionById.containsKey(callId)) { 280 findConnectionForAction(callId, "removeCall") 281 .setDestroyed(); 282 } else { 283 findConferenceForAction(callId, "removeCall") 284 .setDestroyed(); 285 } 286 } 287 288 @Override 289 public void onPostDialWait(String callId, String remaining, Session.Info sessionInfo) { 290 findConnectionForAction(callId, "onPostDialWait") 291 .setPostDialWait(remaining); 292 } 293 294 @Override 295 public void onPostDialChar(String callId, char nextChar, Session.Info sessionInfo) { 296 findConnectionForAction(callId, "onPostDialChar") 297 .onPostDialChar(nextChar); 298 } 299 300 @Override 301 public void queryRemoteConnectionServices(RemoteServiceCallback callback, 302 String callingPackage, Session.Info sessionInfo) { 303 // Not supported from remote connection service. 304 } 305 306 @Override 307 public void setVideoProvider(String callId, IVideoProvider videoProvider, 308 Session.Info sessionInfo) { 309 310 String callingPackage = mOurConnectionServiceImpl.getApplicationContext() 311 .getOpPackageName(); 312 int targetSdkVersion = mOurConnectionServiceImpl.getApplicationInfo().targetSdkVersion; 313 RemoteConnection.VideoProvider remoteVideoProvider = null; 314 if (videoProvider != null) { 315 remoteVideoProvider = new RemoteConnection.VideoProvider(videoProvider, 316 callingPackage, targetSdkVersion); 317 } 318 findConnectionForAction(callId, "setVideoProvider") 319 .setVideoProvider(remoteVideoProvider); 320 } 321 322 @Override 323 public void setVideoState(String callId, int videoState, Session.Info sessionInfo) { 324 findConnectionForAction(callId, "setVideoState") 325 .setVideoState(videoState); 326 } 327 328 @Override 329 public void setIsVoipAudioMode(String callId, boolean isVoip, Session.Info sessionInfo) { 330 findConnectionForAction(callId, "setIsVoipAudioMode") 331 .setIsVoipAudioMode(isVoip); 332 } 333 334 @Override 335 public void setStatusHints(String callId, StatusHints statusHints, 336 Session.Info sessionInfo) { 337 findConnectionForAction(callId, "setStatusHints") 338 .setStatusHints(statusHints); 339 } 340 341 @Override 342 public void setAddress(String callId, Uri address, int presentation, 343 Session.Info sessionInfo) { 344 findConnectionForAction(callId, "setAddress") 345 .setAddress(address, presentation); 346 } 347 348 @Override 349 public void setCallerDisplayName(String callId, String callerDisplayName, 350 int presentation, Session.Info sessionInfo) { 351 findConnectionForAction(callId, "setCallerDisplayName") 352 .setCallerDisplayName(callerDisplayName, presentation); 353 } 354 355 @Override 356 public IBinder asBinder() { 357 throw new UnsupportedOperationException(); 358 } 359 360 @Override 361 public final void setConferenceableConnections(String callId, 362 List<String> conferenceableConnectionIds, Session.Info sessionInfo) { 363 List<RemoteConnection> conferenceable = new ArrayList<>(); 364 for (String id : conferenceableConnectionIds) { 365 if (mConnectionById.containsKey(id)) { 366 conferenceable.add(mConnectionById.get(id)); 367 } 368 } 369 370 if (hasConnection(callId)) { 371 findConnectionForAction(callId, "setConferenceableConnections") 372 .setConferenceableConnections(conferenceable); 373 } else { 374 findConferenceForAction(callId, "setConferenceableConnections") 375 .setConferenceableConnections(conferenceable); 376 } 377 } 378 379 @Override 380 public void addExistingConnection(String callId, ParcelableConnection connection, 381 Session.Info sessionInfo) { 382 String callingPackage = mOurConnectionServiceImpl.getApplicationContext(). 383 getOpPackageName(); 384 int callingTargetSdkVersion = mOurConnectionServiceImpl.getApplicationInfo() 385 .targetSdkVersion; 386 RemoteConnection remoteConnection = new RemoteConnection(callId, 387 mOutgoingConnectionServiceRpc, connection, callingPackage, 388 callingTargetSdkVersion); 389 // Track that it is via a remote connection. 390 Bundle newExtras = new Bundle(); 391 newExtras.putParcelable(Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE, 392 connection.getPhoneAccount()); 393 remoteConnection.putExtras(newExtras); 394 mConnectionById.put(callId, remoteConnection); 395 remoteConnection.registerCallback(new RemoteConnection.Callback() { 396 @Override 397 public void onDestroyed(RemoteConnection connection) { 398 mConnectionById.remove(callId); 399 maybeDisconnectAdapter(); 400 } 401 }); 402 mOurConnectionServiceImpl.addRemoteExistingConnection(remoteConnection); 403 } 404 405 @Override 406 public void putExtras(String callId, Bundle extras, Session.Info sessionInfo) { 407 if (hasConnection(callId)) { 408 findConnectionForAction(callId, "putExtras").putExtras(extras); 409 } else { 410 findConferenceForAction(callId, "putExtras").putExtras(extras); 411 } 412 } 413 414 @Override 415 public void removeExtras(String callId, List<String> keys, Session.Info sessionInfo) { 416 if (hasConnection(callId)) { 417 findConnectionForAction(callId, "removeExtra").removeExtras(keys); 418 } else { 419 findConferenceForAction(callId, "removeExtra").removeExtras(keys); 420 } 421 } 422 423 @Override 424 public void setAudioRoute(String callId, int audioRoute, String bluetoothAddress, 425 Session.Info sessionInfo) { 426 if (hasConnection(callId)) { 427 // TODO(3pcalls): handle this for remote connections. 428 // Likely we don't want to do anything since it doesn't make sense for self-managed 429 // connections to go through a connection mgr. 430 } 431 } 432 433 @Override 434 public void onConnectionEvent(String callId, String event, Bundle extras, 435 Session.Info sessionInfo) { 436 if (mConnectionById.containsKey(callId)) { 437 findConnectionForAction(callId, "onConnectionEvent").onConnectionEvent(event, 438 extras); 439 } 440 } 441 442 @Override 443 public void onRttInitiationSuccess(String callId, Session.Info sessionInfo) 444 throws RemoteException { 445 if (hasConnection(callId)) { 446 findConnectionForAction(callId, "onRttInitiationSuccess") 447 .onRttInitiationSuccess(); 448 } else { 449 Log.w(this, "onRttInitiationSuccess called on a remote conference"); 450 } 451 } 452 453 @Override 454 public void onRttInitiationFailure(String callId, int reason, Session.Info sessionInfo) 455 throws RemoteException { 456 if (hasConnection(callId)) { 457 findConnectionForAction(callId, "onRttInitiationFailure") 458 .onRttInitiationFailure(reason); 459 } else { 460 Log.w(this, "onRttInitiationFailure called on a remote conference"); 461 } 462 } 463 464 @Override 465 public void onRttSessionRemotelyTerminated(String callId, Session.Info sessionInfo) 466 throws RemoteException { 467 if (hasConnection(callId)) { 468 findConnectionForAction(callId, "onRttSessionRemotelyTerminated") 469 .onRttSessionRemotelyTerminated(); 470 } else { 471 Log.w(this, "onRttSessionRemotelyTerminated called on a remote conference"); 472 } 473 } 474 475 @Override 476 public void onRemoteRttRequest(String callId, Session.Info sessionInfo) 477 throws RemoteException { 478 if (hasConnection(callId)) { 479 findConnectionForAction(callId, "onRemoteRttRequest") 480 .onRemoteRttRequest(); 481 } else { 482 Log.w(this, "onRemoteRttRequest called on a remote conference"); 483 } 484 } 485 486 @Override 487 public void resetConnectionTime(String callId, Session.Info sessionInfo) { 488 // Do nothing 489 } 490 491 @Override 492 public void setConferenceState(String callId, boolean isConference, 493 Session.Info sessionInfo) { 494 // Do nothing 495 } 496 497 @Override 498 public void setCallDirection(String callId, int direction, Session.Info sessionInfo) { 499 // Do nothing 500 } 501 }; 502 503 private final ConnectionServiceAdapterServant mServant = 504 new ConnectionServiceAdapterServant(mServantDelegate); 505 506 private final DeathRecipient mDeathRecipient = new DeathRecipient() { 507 @Override 508 public void binderDied() { 509 for (RemoteConnection c : mConnectionById.values()) { 510 c.setDestroyed(); 511 } 512 for (RemoteConference c : mConferenceById.values()) { 513 c.setDestroyed(); 514 } 515 mConnectionById.clear(); 516 mConferenceById.clear(); 517 mPendingConnections.clear(); 518 mOutgoingConnectionServiceRpc.asBinder().unlinkToDeath(mDeathRecipient, 0); 519 } 520 }; 521 522 private final IConnectionService mOutgoingConnectionServiceRpc; 523 private final ConnectionService mOurConnectionServiceImpl; 524 private final Map<String, RemoteConnection> mConnectionById = new HashMap<>(); 525 private final Map<String, RemoteConference> mConferenceById = new HashMap<>(); 526 private final Set<RemoteConnection> mPendingConnections = new HashSet<>(); 527 RemoteConnectionService( IConnectionService outgoingConnectionServiceRpc, ConnectionService ourConnectionServiceImpl)528 RemoteConnectionService( 529 IConnectionService outgoingConnectionServiceRpc, 530 ConnectionService ourConnectionServiceImpl) throws RemoteException { 531 mOutgoingConnectionServiceRpc = outgoingConnectionServiceRpc; 532 mOutgoingConnectionServiceRpc.asBinder().linkToDeath(mDeathRecipient, 0); 533 mOurConnectionServiceImpl = ourConnectionServiceImpl; 534 } 535 536 @Override toString()537 public String toString() { 538 return "[RemoteCS - " + mOutgoingConnectionServiceRpc.asBinder().toString() + "]"; 539 } 540 createRemoteConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request, boolean isIncoming)541 final RemoteConnection createRemoteConnection( 542 PhoneAccountHandle connectionManagerPhoneAccount, 543 ConnectionRequest request, 544 boolean isIncoming) { 545 final String id = UUID.randomUUID().toString(); 546 Bundle extras = new Bundle(); 547 if (request.getExtras() != null) { 548 extras.putAll(request.getExtras()); 549 } 550 // We will set the package name for the originator of the remote request; this lets the 551 // receiving ConnectionService know that the request originated from a remote connection 552 // service so that it can provide tracking information for Telecom. 553 extras.putString(Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME, 554 mOurConnectionServiceImpl.getApplicationContext().getOpPackageName()); 555 556 final ConnectionRequest newRequest = new ConnectionRequest.Builder() 557 .setAccountHandle(request.getAccountHandle()) 558 .setAddress(request.getAddress()) 559 .setExtras(extras) 560 .setVideoState(request.getVideoState()) 561 .setRttPipeFromInCall(request.getRttPipeFromInCall()) 562 .setRttPipeToInCall(request.getRttPipeToInCall()) 563 .build(); 564 try { 565 if (mConnectionById.isEmpty()) { 566 mOutgoingConnectionServiceRpc.addConnectionServiceAdapter(mServant.getStub(), 567 null /*Session.Info*/); 568 } 569 RemoteConnection connection = 570 new RemoteConnection(id, mOutgoingConnectionServiceRpc, newRequest); 571 mPendingConnections.add(connection); 572 mConnectionById.put(id, connection); 573 mOutgoingConnectionServiceRpc.createConnection( 574 connectionManagerPhoneAccount, 575 id, 576 newRequest, 577 isIncoming, 578 false /* isUnknownCall */, 579 null /*Session.info*/); 580 connection.registerCallback(new RemoteConnection.Callback() { 581 @Override 582 public void onDestroyed(RemoteConnection connection) { 583 mConnectionById.remove(id); 584 maybeDisconnectAdapter(); 585 } 586 }); 587 return connection; 588 } catch (RemoteException e) { 589 return RemoteConnection.failure( 590 new DisconnectCause(DisconnectCause.ERROR, e.toString())); 591 } 592 } 593 createRemoteConference( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request, boolean isIncoming)594 RemoteConference createRemoteConference( 595 PhoneAccountHandle connectionManagerPhoneAccount, 596 ConnectionRequest request, 597 boolean isIncoming) { 598 final String id = UUID.randomUUID().toString(); 599 try { 600 if (mConferenceById.isEmpty()) { 601 mOutgoingConnectionServiceRpc.addConnectionServiceAdapter(mServant.getStub(), 602 null /*Session.Info*/); 603 } 604 RemoteConference conference = new RemoteConference(id, mOutgoingConnectionServiceRpc); 605 mOutgoingConnectionServiceRpc.createConference(connectionManagerPhoneAccount, 606 id, 607 request, 608 isIncoming, 609 false /* isUnknownCall */, 610 null /*Session.info*/); 611 conference.registerCallback(new RemoteConference.Callback() { 612 @Override 613 public void onDestroyed(RemoteConference conference) { 614 mConferenceById.remove(id); 615 maybeDisconnectAdapter(); 616 } 617 }); 618 conference.putExtras(request.getExtras()); 619 return conference; 620 } catch (RemoteException e) { 621 return RemoteConference.failure( 622 new DisconnectCause(DisconnectCause.ERROR, e.toString())); 623 } 624 } 625 hasConnection(String callId)626 private boolean hasConnection(String callId) { 627 return mConnectionById.containsKey(callId); 628 } 629 findConnectionForAction( String callId, String action)630 private RemoteConnection findConnectionForAction( 631 String callId, String action) { 632 if (mConnectionById.containsKey(callId)) { 633 return mConnectionById.get(callId); 634 } 635 Log.w(this, "%s - Cannot find Connection %s", action, callId); 636 return NULL_CONNECTION; 637 } 638 findConferenceForAction( String callId, String action)639 private RemoteConference findConferenceForAction( 640 String callId, String action) { 641 if (mConferenceById.containsKey(callId)) { 642 return mConferenceById.get(callId); 643 } 644 Log.w(this, "%s - Cannot find Conference %s", action, callId); 645 return NULL_CONFERENCE; 646 } 647 maybeDisconnectAdapter()648 private void maybeDisconnectAdapter() { 649 if (mConnectionById.isEmpty() && mConferenceById.isEmpty()) { 650 try { 651 mOutgoingConnectionServiceRpc.removeConnectionServiceAdapter(mServant.getStub(), 652 null /*Session.info*/); 653 } catch (RemoteException e) { 654 } 655 } 656 } 657 } 658