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