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; 24 25import com.android.internal.telecom.IConnectionService; 26import com.android.internal.telecom.IConnectionServiceAdapter; 27import com.android.internal.telecom.IVideoProvider; 28import com.android.internal.telecom.RemoteServiceCallback; 29 30import java.util.ArrayList; 31import java.util.HashMap; 32import java.util.HashSet; 33import java.util.Map; 34import java.util.Set; 35import java.util.List; 36import java.util.UUID; 37 38/** 39 * Remote connection service which other connection services can use to place calls on their behalf. 40 * 41 * @hide 42 */ 43final class RemoteConnectionService { 44 45 // Note: Casting null to avoid ambiguous constructor reference. 46 private static final RemoteConnection NULL_CONNECTION = 47 new RemoteConnection("NULL", null, (ConnectionRequest) null); 48 49 private static final RemoteConference NULL_CONFERENCE = 50 new RemoteConference("NULL", null); 51 52 private final IConnectionServiceAdapter mServantDelegate = new IConnectionServiceAdapter() { 53 @Override 54 public void handleCreateConnectionComplete( 55 String id, 56 ConnectionRequest request, 57 ParcelableConnection parcel) { 58 RemoteConnection connection = 59 findConnectionForAction(id, "handleCreateConnectionSuccessful"); 60 if (connection != NULL_CONNECTION && mPendingConnections.contains(connection)) { 61 mPendingConnections.remove(connection); 62 // Unconditionally initialize the connection ... 63 connection.setConnectionCapabilities(parcel.getConnectionCapabilities()); 64 connection.setConnectionProperties(parcel.getConnectionProperties()); 65 if (parcel.getHandle() != null 66 || parcel.getState() != Connection.STATE_DISCONNECTED) { 67 connection.setAddress(parcel.getHandle(), parcel.getHandlePresentation()); 68 } 69 if (parcel.getCallerDisplayName() != null 70 || parcel.getState() != Connection.STATE_DISCONNECTED) { 71 connection.setCallerDisplayName( 72 parcel.getCallerDisplayName(), 73 parcel.getCallerDisplayNamePresentation()); 74 } 75 // Set state after handle so that the client can identify the connection. 76 if (parcel.getState() == Connection.STATE_DISCONNECTED) { 77 connection.setDisconnected(parcel.getDisconnectCause()); 78 } else { 79 connection.setState(parcel.getState()); 80 } 81 List<RemoteConnection> conferenceable = new ArrayList<>(); 82 for (String confId : parcel.getConferenceableConnectionIds()) { 83 if (mConnectionById.containsKey(confId)) { 84 conferenceable.add(mConnectionById.get(confId)); 85 } 86 } 87 connection.setConferenceableConnections(conferenceable); 88 connection.setVideoState(parcel.getVideoState()); 89 if (connection.getState() == Connection.STATE_DISCONNECTED) { 90 // ... then, if it was created in a disconnected state, that indicates 91 // failure on the providing end, so immediately mark it destroyed 92 connection.setDestroyed(); 93 } 94 } 95 } 96 97 @Override 98 public void setActive(String callId) { 99 if (mConnectionById.containsKey(callId)) { 100 findConnectionForAction(callId, "setActive") 101 .setState(Connection.STATE_ACTIVE); 102 } else { 103 findConferenceForAction(callId, "setActive") 104 .setState(Connection.STATE_ACTIVE); 105 } 106 } 107 108 @Override 109 public void setRinging(String callId) { 110 findConnectionForAction(callId, "setRinging") 111 .setState(Connection.STATE_RINGING); 112 } 113 114 @Override 115 public void setDialing(String callId) { 116 findConnectionForAction(callId, "setDialing") 117 .setState(Connection.STATE_DIALING); 118 } 119 120 @Override 121 public void setPulling(String callId) { 122 findConnectionForAction(callId, "setPulling") 123 .setState(Connection.STATE_PULLING_CALL); 124 } 125 126 @Override 127 public void setDisconnected(String callId, DisconnectCause disconnectCause) { 128 if (mConnectionById.containsKey(callId)) { 129 findConnectionForAction(callId, "setDisconnected") 130 .setDisconnected(disconnectCause); 131 } else { 132 findConferenceForAction(callId, "setDisconnected") 133 .setDisconnected(disconnectCause); 134 } 135 } 136 137 @Override 138 public void setOnHold(String callId) { 139 if (mConnectionById.containsKey(callId)) { 140 findConnectionForAction(callId, "setOnHold") 141 .setState(Connection.STATE_HOLDING); 142 } else { 143 findConferenceForAction(callId, "setOnHold") 144 .setState(Connection.STATE_HOLDING); 145 } 146 } 147 148 @Override 149 public void setRingbackRequested(String callId, boolean ringing) { 150 findConnectionForAction(callId, "setRingbackRequested") 151 .setRingbackRequested(ringing); 152 } 153 154 @Override 155 public void setConnectionCapabilities(String callId, int connectionCapabilities) { 156 if (mConnectionById.containsKey(callId)) { 157 findConnectionForAction(callId, "setConnectionCapabilities") 158 .setConnectionCapabilities(connectionCapabilities); 159 } else { 160 findConferenceForAction(callId, "setConnectionCapabilities") 161 .setConnectionCapabilities(connectionCapabilities); 162 } 163 } 164 165 @Override 166 public void setConnectionProperties(String callId, int connectionProperties) { 167 if (mConnectionById.containsKey(callId)) { 168 findConnectionForAction(callId, "setConnectionProperties") 169 .setConnectionProperties(connectionProperties); 170 } else { 171 findConferenceForAction(callId, "setConnectionProperties") 172 .setConnectionProperties(connectionProperties); 173 } 174 } 175 176 @Override 177 public void setIsConferenced(String callId, String conferenceCallId) { 178 // Note: callId should not be null; conferenceCallId may be null 179 RemoteConnection connection = 180 findConnectionForAction(callId, "setIsConferenced"); 181 if (connection != NULL_CONNECTION) { 182 if (conferenceCallId == null) { 183 // 'connection' is being split from its conference 184 if (connection.getConference() != null) { 185 connection.getConference().removeConnection(connection); 186 } 187 } else { 188 RemoteConference conference = 189 findConferenceForAction(conferenceCallId, "setIsConferenced"); 190 if (conference != NULL_CONFERENCE) { 191 conference.addConnection(connection); 192 } 193 } 194 } 195 } 196 197 @Override 198 public void setConferenceMergeFailed(String callId) { 199 // Nothing to do here. 200 // The event has already been handled and there is no state to update 201 // in the underlying connection or conference objects 202 } 203 204 @Override 205 public void addConferenceCall( 206 final String callId, 207 ParcelableConference parcel) { 208 RemoteConference conference = new RemoteConference(callId, 209 mOutgoingConnectionServiceRpc); 210 211 for (String id : parcel.getConnectionIds()) { 212 RemoteConnection c = mConnectionById.get(id); 213 if (c != null) { 214 conference.addConnection(c); 215 } 216 } 217 if (conference.getConnections().size() == 0) { 218 // A conference was created, but none of its connections are ones that have been 219 // created by, and therefore being tracked by, this remote connection service. It 220 // is of no interest to us. 221 Log.d(this, "addConferenceCall - skipping"); 222 return; 223 } 224 225 conference.setState(parcel.getState()); 226 conference.setConnectionCapabilities(parcel.getConnectionCapabilities()); 227 conference.setConnectionProperties(parcel.getConnectionProperties()); 228 conference.putExtras(parcel.getExtras()); 229 mConferenceById.put(callId, conference); 230 231 // Stash the original connection ID as it exists in the source ConnectionService. 232 // Telecom will use this to avoid adding duplicates later. 233 // See comments on Connection.EXTRA_ORIGINAL_CONNECTION_ID for more information. 234 Bundle newExtras = new Bundle(); 235 newExtras.putString(Connection.EXTRA_ORIGINAL_CONNECTION_ID, callId); 236 conference.putExtras(newExtras); 237 238 conference.registerCallback(new RemoteConference.Callback() { 239 @Override 240 public void onDestroyed(RemoteConference c) { 241 mConferenceById.remove(callId); 242 maybeDisconnectAdapter(); 243 } 244 }); 245 246 mOurConnectionServiceImpl.addRemoteConference(conference); 247 } 248 249 @Override 250 public void removeCall(String callId) { 251 if (mConnectionById.containsKey(callId)) { 252 findConnectionForAction(callId, "removeCall") 253 .setDestroyed(); 254 } else { 255 findConferenceForAction(callId, "removeCall") 256 .setDestroyed(); 257 } 258 } 259 260 @Override 261 public void onPostDialWait(String callId, String remaining) { 262 findConnectionForAction(callId, "onPostDialWait") 263 .setPostDialWait(remaining); 264 } 265 266 @Override 267 public void onPostDialChar(String callId, char nextChar) { 268 findConnectionForAction(callId, "onPostDialChar") 269 .onPostDialChar(nextChar); 270 } 271 272 @Override 273 public void queryRemoteConnectionServices(RemoteServiceCallback callback) { 274 // Not supported from remote connection service. 275 } 276 277 @Override 278 public void setVideoProvider(String callId, IVideoProvider videoProvider) { 279 RemoteConnection.VideoProvider remoteVideoProvider = null; 280 if (videoProvider != null) { 281 remoteVideoProvider = new RemoteConnection.VideoProvider(videoProvider); 282 } 283 findConnectionForAction(callId, "setVideoProvider") 284 .setVideoProvider(remoteVideoProvider); 285 } 286 287 @Override 288 public void setVideoState(String callId, int videoState) { 289 findConnectionForAction(callId, "setVideoState") 290 .setVideoState(videoState); 291 } 292 293 @Override 294 public void setIsVoipAudioMode(String callId, boolean isVoip) { 295 findConnectionForAction(callId, "setIsVoipAudioMode") 296 .setIsVoipAudioMode(isVoip); 297 } 298 299 @Override 300 public void setStatusHints(String callId, StatusHints statusHints) { 301 findConnectionForAction(callId, "setStatusHints") 302 .setStatusHints(statusHints); 303 } 304 305 @Override 306 public void setAddress(String callId, Uri address, int presentation) { 307 findConnectionForAction(callId, "setAddress") 308 .setAddress(address, presentation); 309 } 310 311 @Override 312 public void setCallerDisplayName(String callId, String callerDisplayName, 313 int presentation) { 314 findConnectionForAction(callId, "setCallerDisplayName") 315 .setCallerDisplayName(callerDisplayName, presentation); 316 } 317 318 @Override 319 public IBinder asBinder() { 320 throw new UnsupportedOperationException(); 321 } 322 323 @Override 324 public final void setConferenceableConnections( 325 String callId, List<String> conferenceableConnectionIds) { 326 List<RemoteConnection> conferenceable = new ArrayList<>(); 327 for (String id : conferenceableConnectionIds) { 328 if (mConnectionById.containsKey(id)) { 329 conferenceable.add(mConnectionById.get(id)); 330 } 331 } 332 333 if (hasConnection(callId)) { 334 findConnectionForAction(callId, "setConferenceableConnections") 335 .setConferenceableConnections(conferenceable); 336 } else { 337 findConferenceForAction(callId, "setConferenceableConnections") 338 .setConferenceableConnections(conferenceable); 339 } 340 } 341 342 @Override 343 public void addExistingConnection(final String callId, ParcelableConnection connection) { 344 RemoteConnection remoteConnection = new RemoteConnection(callId, 345 mOutgoingConnectionServiceRpc, connection); 346 mConnectionById.put(callId, remoteConnection); 347 remoteConnection.registerCallback(new RemoteConnection.Callback() { 348 @Override 349 public void onDestroyed(RemoteConnection connection) { 350 mConnectionById.remove(callId); 351 maybeDisconnectAdapter(); 352 } 353 }); 354 mOurConnectionServiceImpl.addRemoteExistingConnection(remoteConnection); 355 } 356 357 @Override 358 public void putExtras(String callId, Bundle extras) { 359 if (hasConnection(callId)) { 360 findConnectionForAction(callId, "putExtras").putExtras(extras); 361 } else { 362 findConferenceForAction(callId, "putExtras").putExtras(extras); 363 } 364 } 365 366 @Override 367 public void removeExtras(String callId, List<String> keys) { 368 if (hasConnection(callId)) { 369 findConnectionForAction(callId, "removeExtra").removeExtras(keys); 370 } else { 371 findConferenceForAction(callId, "removeExtra").removeExtras(keys); 372 } 373 } 374 375 @Override 376 public void onConnectionEvent(String callId, String event, Bundle extras) { 377 if (mConnectionById.containsKey(callId)) { 378 findConnectionForAction(callId, "onConnectionEvent").onConnectionEvent(event, 379 extras); 380 } 381 } 382 }; 383 384 private final ConnectionServiceAdapterServant mServant = 385 new ConnectionServiceAdapterServant(mServantDelegate); 386 387 private final DeathRecipient mDeathRecipient = new DeathRecipient() { 388 @Override 389 public void binderDied() { 390 for (RemoteConnection c : mConnectionById.values()) { 391 c.setDestroyed(); 392 } 393 for (RemoteConference c : mConferenceById.values()) { 394 c.setDestroyed(); 395 } 396 mConnectionById.clear(); 397 mConferenceById.clear(); 398 mPendingConnections.clear(); 399 mOutgoingConnectionServiceRpc.asBinder().unlinkToDeath(mDeathRecipient, 0); 400 } 401 }; 402 403 private final IConnectionService mOutgoingConnectionServiceRpc; 404 private final ConnectionService mOurConnectionServiceImpl; 405 private final Map<String, RemoteConnection> mConnectionById = new HashMap<>(); 406 private final Map<String, RemoteConference> mConferenceById = new HashMap<>(); 407 private final Set<RemoteConnection> mPendingConnections = new HashSet<>(); 408 409 RemoteConnectionService( 410 IConnectionService outgoingConnectionServiceRpc, 411 ConnectionService ourConnectionServiceImpl) throws RemoteException { 412 mOutgoingConnectionServiceRpc = outgoingConnectionServiceRpc; 413 mOutgoingConnectionServiceRpc.asBinder().linkToDeath(mDeathRecipient, 0); 414 mOurConnectionServiceImpl = ourConnectionServiceImpl; 415 } 416 417 @Override 418 public String toString() { 419 return "[RemoteCS - " + mOutgoingConnectionServiceRpc.asBinder().toString() + "]"; 420 } 421 422 final RemoteConnection createRemoteConnection( 423 PhoneAccountHandle connectionManagerPhoneAccount, 424 ConnectionRequest request, 425 boolean isIncoming) { 426 final String id = UUID.randomUUID().toString(); 427 final ConnectionRequest newRequest = new ConnectionRequest( 428 request.getAccountHandle(), 429 request.getAddress(), 430 request.getExtras(), 431 request.getVideoState()); 432 try { 433 if (mConnectionById.isEmpty()) { 434 mOutgoingConnectionServiceRpc.addConnectionServiceAdapter(mServant.getStub()); 435 } 436 RemoteConnection connection = 437 new RemoteConnection(id, mOutgoingConnectionServiceRpc, newRequest); 438 mPendingConnections.add(connection); 439 mConnectionById.put(id, connection); 440 mOutgoingConnectionServiceRpc.createConnection( 441 connectionManagerPhoneAccount, 442 id, 443 newRequest, 444 isIncoming, 445 false /* isUnknownCall */); 446 connection.registerCallback(new RemoteConnection.Callback() { 447 @Override 448 public void onDestroyed(RemoteConnection connection) { 449 mConnectionById.remove(id); 450 maybeDisconnectAdapter(); 451 } 452 }); 453 return connection; 454 } catch (RemoteException e) { 455 return RemoteConnection.failure( 456 new DisconnectCause(DisconnectCause.ERROR, e.toString())); 457 } 458 } 459 460 private boolean hasConnection(String callId) { 461 return mConnectionById.containsKey(callId); 462 } 463 464 private RemoteConnection findConnectionForAction( 465 String callId, String action) { 466 if (mConnectionById.containsKey(callId)) { 467 return mConnectionById.get(callId); 468 } 469 Log.w(this, "%s - Cannot find Connection %s", action, callId); 470 return NULL_CONNECTION; 471 } 472 473 private RemoteConference findConferenceForAction( 474 String callId, String action) { 475 if (mConferenceById.containsKey(callId)) { 476 return mConferenceById.get(callId); 477 } 478 Log.w(this, "%s - Cannot find Conference %s", action, callId); 479 return NULL_CONFERENCE; 480 } 481 482 private void maybeDisconnectAdapter() { 483 if (mConnectionById.isEmpty() && mConferenceById.isEmpty()) { 484 try { 485 mOutgoingConnectionServiceRpc.removeConnectionServiceAdapter(mServant.getStub()); 486 } catch (RemoteException e) { 487 } 488 } 489 } 490} 491