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 17package android.telecom; 18 19import android.net.Uri; 20import android.os.Bundle; 21import android.os.IBinder; 22import android.os.IBinder.DeathRecipient; 23import android.os.RemoteException; 24import android.telecom.Logging.Session; 25 26import com.android.internal.telecom.IConnectionService; 27import com.android.internal.telecom.IConnectionServiceAdapter; 28import com.android.internal.telecom.IVideoProvider; 29import com.android.internal.telecom.RemoteServiceCallback; 30 31import java.util.ArrayList; 32import java.util.HashMap; 33import java.util.HashSet; 34import java.util.Map; 35import java.util.Set; 36import java.util.List; 37import 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 */ 44final 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 setActive(String callId, Session.Info sessionInfo) { 105 if (mConnectionById.containsKey(callId)) { 106 findConnectionForAction(callId, "setActive") 107 .setState(Connection.STATE_ACTIVE); 108 } else { 109 findConferenceForAction(callId, "setActive") 110 .setState(Connection.STATE_ACTIVE); 111 } 112 } 113 114 @Override 115 public void setRinging(String callId, Session.Info sessionInfo) { 116 findConnectionForAction(callId, "setRinging") 117 .setState(Connection.STATE_RINGING); 118 } 119 120 @Override 121 public void setDialing(String callId, Session.Info sessionInfo) { 122 findConnectionForAction(callId, "setDialing") 123 .setState(Connection.STATE_DIALING); 124 } 125 126 @Override 127 public void setPulling(String callId, Session.Info sessionInfo) { 128 findConnectionForAction(callId, "setPulling") 129 .setState(Connection.STATE_PULLING_CALL); 130 } 131 132 @Override 133 public void setDisconnected(String callId, DisconnectCause disconnectCause, 134 Session.Info sessionInfo) { 135 if (mConnectionById.containsKey(callId)) { 136 findConnectionForAction(callId, "setDisconnected") 137 .setDisconnected(disconnectCause); 138 } else { 139 findConferenceForAction(callId, "setDisconnected") 140 .setDisconnected(disconnectCause); 141 } 142 } 143 144 @Override 145 public void setOnHold(String callId, Session.Info sessionInfo) { 146 if (mConnectionById.containsKey(callId)) { 147 findConnectionForAction(callId, "setOnHold") 148 .setState(Connection.STATE_HOLDING); 149 } else { 150 findConferenceForAction(callId, "setOnHold") 151 .setState(Connection.STATE_HOLDING); 152 } 153 } 154 155 @Override 156 public void setRingbackRequested(String callId, boolean ringing, Session.Info sessionInfo) { 157 findConnectionForAction(callId, "setRingbackRequested") 158 .setRingbackRequested(ringing); 159 } 160 161 @Override 162 public void setConnectionCapabilities(String callId, int connectionCapabilities, 163 Session.Info sessionInfo) { 164 if (mConnectionById.containsKey(callId)) { 165 findConnectionForAction(callId, "setConnectionCapabilities") 166 .setConnectionCapabilities(connectionCapabilities); 167 } else { 168 findConferenceForAction(callId, "setConnectionCapabilities") 169 .setConnectionCapabilities(connectionCapabilities); 170 } 171 } 172 173 @Override 174 public void setConnectionProperties(String callId, int connectionProperties, 175 Session.Info sessionInfo) { 176 if (mConnectionById.containsKey(callId)) { 177 findConnectionForAction(callId, "setConnectionProperties") 178 .setConnectionProperties(connectionProperties); 179 } else { 180 findConferenceForAction(callId, "setConnectionProperties") 181 .setConnectionProperties(connectionProperties); 182 } 183 } 184 185 @Override 186 public void setIsConferenced(String callId, String conferenceCallId, 187 Session.Info sessionInfo) { 188 // Note: callId should not be null; conferenceCallId may be null 189 RemoteConnection connection = 190 findConnectionForAction(callId, "setIsConferenced"); 191 if (connection != NULL_CONNECTION) { 192 if (conferenceCallId == null) { 193 // 'connection' is being split from its conference 194 if (connection.getConference() != null) { 195 connection.getConference().removeConnection(connection); 196 } 197 } else { 198 RemoteConference conference = 199 findConferenceForAction(conferenceCallId, "setIsConferenced"); 200 if (conference != NULL_CONFERENCE) { 201 conference.addConnection(connection); 202 } 203 } 204 } 205 } 206 207 @Override 208 public void setConferenceMergeFailed(String callId, Session.Info sessionInfo) { 209 // Nothing to do here. 210 // The event has already been handled and there is no state to update 211 // in the underlying connection or conference objects 212 } 213 214 @Override 215 public void onPhoneAccountChanged(String callId, PhoneAccountHandle pHandle, 216 Session.Info sessionInfo) { 217 } 218 219 @Override 220 public void onConnectionServiceFocusReleased(Session.Info sessionInfo) {} 221 222 @Override 223 public void addConferenceCall( 224 final String callId, ParcelableConference parcel, Session.Info sessionInfo) { 225 RemoteConference conference = new RemoteConference(callId, 226 mOutgoingConnectionServiceRpc); 227 228 for (String id : parcel.getConnectionIds()) { 229 RemoteConnection c = mConnectionById.get(id); 230 if (c != null) { 231 conference.addConnection(c); 232 } 233 } 234 if (conference.getConnections().size() == 0) { 235 // A conference was created, but none of its connections are ones that have been 236 // created by, and therefore being tracked by, this remote connection service. It 237 // is of no interest to us. 238 Log.d(this, "addConferenceCall - skipping"); 239 return; 240 } 241 242 conference.setState(parcel.getState()); 243 conference.setConnectionCapabilities(parcel.getConnectionCapabilities()); 244 conference.setConnectionProperties(parcel.getConnectionProperties()); 245 conference.putExtras(parcel.getExtras()); 246 mConferenceById.put(callId, conference); 247 248 // Stash the original connection ID as it exists in the source ConnectionService. 249 // Telecom will use this to avoid adding duplicates later. 250 // See comments on Connection.EXTRA_ORIGINAL_CONNECTION_ID for more information. 251 Bundle newExtras = new Bundle(); 252 newExtras.putString(Connection.EXTRA_ORIGINAL_CONNECTION_ID, callId); 253 conference.putExtras(newExtras); 254 255 conference.registerCallback(new RemoteConference.Callback() { 256 @Override 257 public void onDestroyed(RemoteConference c) { 258 mConferenceById.remove(callId); 259 maybeDisconnectAdapter(); 260 } 261 }); 262 263 mOurConnectionServiceImpl.addRemoteConference(conference); 264 } 265 266 @Override 267 public void removeCall(String callId, Session.Info sessionInfo) { 268 if (mConnectionById.containsKey(callId)) { 269 findConnectionForAction(callId, "removeCall") 270 .setDestroyed(); 271 } else { 272 findConferenceForAction(callId, "removeCall") 273 .setDestroyed(); 274 } 275 } 276 277 @Override 278 public void onPostDialWait(String callId, String remaining, Session.Info sessionInfo) { 279 findConnectionForAction(callId, "onPostDialWait") 280 .setPostDialWait(remaining); 281 } 282 283 @Override 284 public void onPostDialChar(String callId, char nextChar, Session.Info sessionInfo) { 285 findConnectionForAction(callId, "onPostDialChar") 286 .onPostDialChar(nextChar); 287 } 288 289 @Override 290 public void queryRemoteConnectionServices(RemoteServiceCallback callback, 291 Session.Info sessionInfo) { 292 // Not supported from remote connection service. 293 } 294 295 @Override 296 public void setVideoProvider(String callId, IVideoProvider videoProvider, 297 Session.Info sessionInfo) { 298 299 String callingPackage = mOurConnectionServiceImpl.getApplicationContext() 300 .getOpPackageName(); 301 int targetSdkVersion = mOurConnectionServiceImpl.getApplicationInfo().targetSdkVersion; 302 RemoteConnection.VideoProvider remoteVideoProvider = null; 303 if (videoProvider != null) { 304 remoteVideoProvider = new RemoteConnection.VideoProvider(videoProvider, 305 callingPackage, targetSdkVersion); 306 } 307 findConnectionForAction(callId, "setVideoProvider") 308 .setVideoProvider(remoteVideoProvider); 309 } 310 311 @Override 312 public void setVideoState(String callId, int videoState, Session.Info sessionInfo) { 313 findConnectionForAction(callId, "setVideoState") 314 .setVideoState(videoState); 315 } 316 317 @Override 318 public void setIsVoipAudioMode(String callId, boolean isVoip, Session.Info sessionInfo) { 319 findConnectionForAction(callId, "setIsVoipAudioMode") 320 .setIsVoipAudioMode(isVoip); 321 } 322 323 @Override 324 public void setStatusHints(String callId, StatusHints statusHints, 325 Session.Info sessionInfo) { 326 findConnectionForAction(callId, "setStatusHints") 327 .setStatusHints(statusHints); 328 } 329 330 @Override 331 public void setAddress(String callId, Uri address, int presentation, 332 Session.Info sessionInfo) { 333 findConnectionForAction(callId, "setAddress") 334 .setAddress(address, presentation); 335 } 336 337 @Override 338 public void setCallerDisplayName(String callId, String callerDisplayName, 339 int presentation, Session.Info sessionInfo) { 340 findConnectionForAction(callId, "setCallerDisplayName") 341 .setCallerDisplayName(callerDisplayName, presentation); 342 } 343 344 @Override 345 public IBinder asBinder() { 346 throw new UnsupportedOperationException(); 347 } 348 349 @Override 350 public final void setConferenceableConnections(String callId, 351 List<String> conferenceableConnectionIds, Session.Info sessionInfo) { 352 List<RemoteConnection> conferenceable = new ArrayList<>(); 353 for (String id : conferenceableConnectionIds) { 354 if (mConnectionById.containsKey(id)) { 355 conferenceable.add(mConnectionById.get(id)); 356 } 357 } 358 359 if (hasConnection(callId)) { 360 findConnectionForAction(callId, "setConferenceableConnections") 361 .setConferenceableConnections(conferenceable); 362 } else { 363 findConferenceForAction(callId, "setConferenceableConnections") 364 .setConferenceableConnections(conferenceable); 365 } 366 } 367 368 @Override 369 public void addExistingConnection(String callId, ParcelableConnection connection, 370 Session.Info sessionInfo) { 371 String callingPackage = mOurConnectionServiceImpl.getApplicationContext(). 372 getOpPackageName(); 373 int callingTargetSdkVersion = mOurConnectionServiceImpl.getApplicationInfo() 374 .targetSdkVersion; 375 RemoteConnection remoteConnection = new RemoteConnection(callId, 376 mOutgoingConnectionServiceRpc, connection, callingPackage, 377 callingTargetSdkVersion); 378 mConnectionById.put(callId, remoteConnection); 379 remoteConnection.registerCallback(new RemoteConnection.Callback() { 380 @Override 381 public void onDestroyed(RemoteConnection connection) { 382 mConnectionById.remove(callId); 383 maybeDisconnectAdapter(); 384 } 385 }); 386 mOurConnectionServiceImpl.addRemoteExistingConnection(remoteConnection); 387 } 388 389 @Override 390 public void putExtras(String callId, Bundle extras, Session.Info sessionInfo) { 391 if (hasConnection(callId)) { 392 findConnectionForAction(callId, "putExtras").putExtras(extras); 393 } else { 394 findConferenceForAction(callId, "putExtras").putExtras(extras); 395 } 396 } 397 398 @Override 399 public void removeExtras(String callId, List<String> keys, Session.Info sessionInfo) { 400 if (hasConnection(callId)) { 401 findConnectionForAction(callId, "removeExtra").removeExtras(keys); 402 } else { 403 findConferenceForAction(callId, "removeExtra").removeExtras(keys); 404 } 405 } 406 407 @Override 408 public void setAudioRoute(String callId, int audioRoute, String bluetoothAddress, 409 Session.Info sessionInfo) { 410 if (hasConnection(callId)) { 411 // TODO(3pcalls): handle this for remote connections. 412 // Likely we don't want to do anything since it doesn't make sense for self-managed 413 // connections to go through a connection mgr. 414 } 415 } 416 417 @Override 418 public void onConnectionEvent(String callId, String event, Bundle extras, 419 Session.Info sessionInfo) { 420 if (mConnectionById.containsKey(callId)) { 421 findConnectionForAction(callId, "onConnectionEvent").onConnectionEvent(event, 422 extras); 423 } 424 } 425 426 @Override 427 public void onRttInitiationSuccess(String callId, Session.Info sessionInfo) 428 throws RemoteException { 429 if (hasConnection(callId)) { 430 findConnectionForAction(callId, "onRttInitiationSuccess") 431 .onRttInitiationSuccess(); 432 } else { 433 Log.w(this, "onRttInitiationSuccess called on a remote conference"); 434 } 435 } 436 437 @Override 438 public void onRttInitiationFailure(String callId, int reason, Session.Info sessionInfo) 439 throws RemoteException { 440 if (hasConnection(callId)) { 441 findConnectionForAction(callId, "onRttInitiationFailure") 442 .onRttInitiationFailure(reason); 443 } else { 444 Log.w(this, "onRttInitiationFailure called on a remote conference"); 445 } 446 } 447 448 @Override 449 public void onRttSessionRemotelyTerminated(String callId, Session.Info sessionInfo) 450 throws RemoteException { 451 if (hasConnection(callId)) { 452 findConnectionForAction(callId, "onRttSessionRemotelyTerminated") 453 .onRttSessionRemotelyTerminated(); 454 } else { 455 Log.w(this, "onRttSessionRemotelyTerminated called on a remote conference"); 456 } 457 } 458 459 @Override 460 public void onRemoteRttRequest(String callId, Session.Info sessionInfo) 461 throws RemoteException { 462 if (hasConnection(callId)) { 463 findConnectionForAction(callId, "onRemoteRttRequest") 464 .onRemoteRttRequest(); 465 } else { 466 Log.w(this, "onRemoteRttRequest called on a remote conference"); 467 } 468 } 469 }; 470 471 private final ConnectionServiceAdapterServant mServant = 472 new ConnectionServiceAdapterServant(mServantDelegate); 473 474 private final DeathRecipient mDeathRecipient = new DeathRecipient() { 475 @Override 476 public void binderDied() { 477 for (RemoteConnection c : mConnectionById.values()) { 478 c.setDestroyed(); 479 } 480 for (RemoteConference c : mConferenceById.values()) { 481 c.setDestroyed(); 482 } 483 mConnectionById.clear(); 484 mConferenceById.clear(); 485 mPendingConnections.clear(); 486 mOutgoingConnectionServiceRpc.asBinder().unlinkToDeath(mDeathRecipient, 0); 487 } 488 }; 489 490 private final IConnectionService mOutgoingConnectionServiceRpc; 491 private final ConnectionService mOurConnectionServiceImpl; 492 private final Map<String, RemoteConnection> mConnectionById = new HashMap<>(); 493 private final Map<String, RemoteConference> mConferenceById = new HashMap<>(); 494 private final Set<RemoteConnection> mPendingConnections = new HashSet<>(); 495 496 RemoteConnectionService( 497 IConnectionService outgoingConnectionServiceRpc, 498 ConnectionService ourConnectionServiceImpl) throws RemoteException { 499 mOutgoingConnectionServiceRpc = outgoingConnectionServiceRpc; 500 mOutgoingConnectionServiceRpc.asBinder().linkToDeath(mDeathRecipient, 0); 501 mOurConnectionServiceImpl = ourConnectionServiceImpl; 502 } 503 504 @Override 505 public String toString() { 506 return "[RemoteCS - " + mOutgoingConnectionServiceRpc.asBinder().toString() + "]"; 507 } 508 509 final RemoteConnection createRemoteConnection( 510 PhoneAccountHandle connectionManagerPhoneAccount, 511 ConnectionRequest request, 512 boolean isIncoming) { 513 final String id = UUID.randomUUID().toString(); 514 final ConnectionRequest newRequest = new ConnectionRequest.Builder() 515 .setAccountHandle(request.getAccountHandle()) 516 .setAddress(request.getAddress()) 517 .setExtras(request.getExtras()) 518 .setVideoState(request.getVideoState()) 519 .setRttPipeFromInCall(request.getRttPipeFromInCall()) 520 .setRttPipeToInCall(request.getRttPipeToInCall()) 521 .build(); 522 try { 523 if (mConnectionById.isEmpty()) { 524 mOutgoingConnectionServiceRpc.addConnectionServiceAdapter(mServant.getStub(), 525 null /*Session.Info*/); 526 } 527 RemoteConnection connection = 528 new RemoteConnection(id, mOutgoingConnectionServiceRpc, newRequest); 529 mPendingConnections.add(connection); 530 mConnectionById.put(id, connection); 531 mOutgoingConnectionServiceRpc.createConnection( 532 connectionManagerPhoneAccount, 533 id, 534 newRequest, 535 isIncoming, 536 false /* isUnknownCall */, 537 null /*Session.info*/); 538 connection.registerCallback(new RemoteConnection.Callback() { 539 @Override 540 public void onDestroyed(RemoteConnection connection) { 541 mConnectionById.remove(id); 542 maybeDisconnectAdapter(); 543 } 544 }); 545 return connection; 546 } catch (RemoteException e) { 547 return RemoteConnection.failure( 548 new DisconnectCause(DisconnectCause.ERROR, e.toString())); 549 } 550 } 551 552 private boolean hasConnection(String callId) { 553 return mConnectionById.containsKey(callId); 554 } 555 556 private RemoteConnection findConnectionForAction( 557 String callId, String action) { 558 if (mConnectionById.containsKey(callId)) { 559 return mConnectionById.get(callId); 560 } 561 Log.w(this, "%s - Cannot find Connection %s", action, callId); 562 return NULL_CONNECTION; 563 } 564 565 private RemoteConference findConferenceForAction( 566 String callId, String action) { 567 if (mConferenceById.containsKey(callId)) { 568 return mConferenceById.get(callId); 569 } 570 Log.w(this, "%s - Cannot find Conference %s", action, callId); 571 return NULL_CONFERENCE; 572 } 573 574 private void maybeDisconnectAdapter() { 575 if (mConnectionById.isEmpty() && mConferenceById.isEmpty()) { 576 try { 577 mOutgoingConnectionServiceRpc.removeConnectionServiceAdapter(mServant.getStub(), 578 null /*Session.info*/); 579 } catch (RemoteException e) { 580 } 581 } 582 } 583} 584