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 setDisconnected(String callId, DisconnectCause disconnectCause) { 122 if (mConnectionById.containsKey(callId)) { 123 findConnectionForAction(callId, "setDisconnected") 124 .setDisconnected(disconnectCause); 125 } else { 126 findConferenceForAction(callId, "setDisconnected") 127 .setDisconnected(disconnectCause); 128 } 129 } 130 131 @Override 132 public void setOnHold(String callId) { 133 if (mConnectionById.containsKey(callId)) { 134 findConnectionForAction(callId, "setOnHold") 135 .setState(Connection.STATE_HOLDING); 136 } else { 137 findConferenceForAction(callId, "setOnHold") 138 .setState(Connection.STATE_HOLDING); 139 } 140 } 141 142 @Override 143 public void setRingbackRequested(String callId, boolean ringing) { 144 findConnectionForAction(callId, "setRingbackRequested") 145 .setRingbackRequested(ringing); 146 } 147 148 @Override 149 public void setConnectionCapabilities(String callId, int connectionCapabilities) { 150 if (mConnectionById.containsKey(callId)) { 151 findConnectionForAction(callId, "setConnectionCapabilities") 152 .setConnectionCapabilities(connectionCapabilities); 153 } else { 154 findConferenceForAction(callId, "setConnectionCapabilities") 155 .setConnectionCapabilities(connectionCapabilities); 156 } 157 } 158 159 @Override 160 public void setConnectionProperties(String callId, int connectionProperties) { 161 if (mConnectionById.containsKey(callId)) { 162 findConnectionForAction(callId, "setConnectionProperties") 163 .setConnectionProperties(connectionProperties); 164 } else { 165 findConferenceForAction(callId, "setConnectionProperties") 166 .setConnectionProperties(connectionProperties); 167 } 168 } 169 170 @Override 171 public void setIsConferenced(String callId, String conferenceCallId) { 172 // Note: callId should not be null; conferenceCallId may be null 173 RemoteConnection connection = 174 findConnectionForAction(callId, "setIsConferenced"); 175 if (connection != NULL_CONNECTION) { 176 if (conferenceCallId == null) { 177 // 'connection' is being split from its conference 178 if (connection.getConference() != null) { 179 connection.getConference().removeConnection(connection); 180 } 181 } else { 182 RemoteConference conference = 183 findConferenceForAction(conferenceCallId, "setIsConferenced"); 184 if (conference != NULL_CONFERENCE) { 185 conference.addConnection(connection); 186 } 187 } 188 } 189 } 190 191 @Override 192 public void setConferenceMergeFailed(String callId) { 193 // Nothing to do here. 194 // The event has already been handled and there is no state to update 195 // in the underlying connection or conference objects 196 } 197 198 @Override 199 public void addConferenceCall( 200 final String callId, 201 ParcelableConference parcel) { 202 RemoteConference conference = new RemoteConference(callId, 203 mOutgoingConnectionServiceRpc); 204 205 for (String id : parcel.getConnectionIds()) { 206 RemoteConnection c = mConnectionById.get(id); 207 if (c != null) { 208 conference.addConnection(c); 209 } 210 } 211 212 if (conference.getConnections().size() == 0) { 213 // A conference was created, but none of its connections are ones that have been 214 // created by, and therefore being tracked by, this remote connection service. It 215 // is of no interest to us. 216 return; 217 } 218 219 conference.setState(parcel.getState()); 220 conference.setConnectionCapabilities(parcel.getConnectionCapabilities()); 221 mConferenceById.put(callId, conference); 222 conference.registerCallback(new RemoteConference.Callback() { 223 @Override 224 public void onDestroyed(RemoteConference c) { 225 mConferenceById.remove(callId); 226 maybeDisconnectAdapter(); 227 } 228 }); 229 230 mOurConnectionServiceImpl.addRemoteConference(conference); 231 } 232 233 @Override 234 public void removeCall(String callId) { 235 if (mConnectionById.containsKey(callId)) { 236 findConnectionForAction(callId, "removeCall") 237 .setDestroyed(); 238 } else { 239 findConferenceForAction(callId, "removeCall") 240 .setDestroyed(); 241 } 242 } 243 244 @Override 245 public void onPostDialWait(String callId, String remaining) { 246 findConnectionForAction(callId, "onPostDialWait") 247 .setPostDialWait(remaining); 248 } 249 250 @Override 251 public void onPostDialChar(String callId, char nextChar) { 252 findConnectionForAction(callId, "onPostDialChar") 253 .onPostDialChar(nextChar); 254 } 255 256 @Override 257 public void queryRemoteConnectionServices(RemoteServiceCallback callback) { 258 // Not supported from remote connection service. 259 } 260 261 @Override 262 public void setVideoProvider(String callId, IVideoProvider videoProvider) { 263 RemoteConnection.VideoProvider remoteVideoProvider = null; 264 if (videoProvider != null) { 265 remoteVideoProvider = new RemoteConnection.VideoProvider(videoProvider); 266 } 267 findConnectionForAction(callId, "setVideoProvider") 268 .setVideoProvider(remoteVideoProvider); 269 } 270 271 @Override 272 public void setVideoState(String callId, int videoState) { 273 findConnectionForAction(callId, "setVideoState") 274 .setVideoState(videoState); 275 } 276 277 @Override 278 public void setIsVoipAudioMode(String callId, boolean isVoip) { 279 findConnectionForAction(callId, "setIsVoipAudioMode") 280 .setIsVoipAudioMode(isVoip); 281 } 282 283 @Override 284 public void setStatusHints(String callId, StatusHints statusHints) { 285 findConnectionForAction(callId, "setStatusHints") 286 .setStatusHints(statusHints); 287 } 288 289 @Override 290 public void setAddress(String callId, Uri address, int presentation) { 291 findConnectionForAction(callId, "setAddress") 292 .setAddress(address, presentation); 293 } 294 295 @Override 296 public void setCallerDisplayName(String callId, String callerDisplayName, 297 int presentation) { 298 findConnectionForAction(callId, "setCallerDisplayName") 299 .setCallerDisplayName(callerDisplayName, presentation); 300 } 301 302 @Override 303 public IBinder asBinder() { 304 throw new UnsupportedOperationException(); 305 } 306 307 @Override 308 public final void setConferenceableConnections( 309 String callId, List<String> conferenceableConnectionIds) { 310 List<RemoteConnection> conferenceable = new ArrayList<>(); 311 for (String id : conferenceableConnectionIds) { 312 if (mConnectionById.containsKey(id)) { 313 conferenceable.add(mConnectionById.get(id)); 314 } 315 } 316 317 if (hasConnection(callId)) { 318 findConnectionForAction(callId, "setConferenceableConnections") 319 .setConferenceableConnections(conferenceable); 320 } else { 321 findConferenceForAction(callId, "setConferenceableConnections") 322 .setConferenceableConnections(conferenceable); 323 } 324 } 325 326 @Override 327 public void addExistingConnection(String callId, ParcelableConnection connection) { 328 // TODO: add contents of this method 329 RemoteConnection remoteConnction = new RemoteConnection(callId, 330 mOutgoingConnectionServiceRpc, connection); 331 332 mOurConnectionServiceImpl.addRemoteExistingConnection(remoteConnction); 333 } 334 335 @Override 336 public void putExtras(String callId, Bundle extras) { 337 if (hasConnection(callId)) { 338 findConnectionForAction(callId, "putExtras").putExtras(extras); 339 } else { 340 findConferenceForAction(callId, "putExtras").putExtras(extras); 341 } 342 } 343 344 @Override 345 public void removeExtras(String callId, List<String> keys) { 346 if (hasConnection(callId)) { 347 findConnectionForAction(callId, "removeExtra").removeExtras(keys); 348 } else { 349 findConferenceForAction(callId, "removeExtra").removeExtras(keys); 350 } 351 } 352 353 @Override 354 public void onConnectionEvent(String callId, String event, Bundle extras) { 355 if (mConnectionById.containsKey(callId)) { 356 findConnectionForAction(callId, "onConnectionEvent").onConnectionEvent(event, 357 extras); 358 } 359 } 360 }; 361 362 private final ConnectionServiceAdapterServant mServant = 363 new ConnectionServiceAdapterServant(mServantDelegate); 364 365 private final DeathRecipient mDeathRecipient = new DeathRecipient() { 366 @Override 367 public void binderDied() { 368 for (RemoteConnection c : mConnectionById.values()) { 369 c.setDestroyed(); 370 } 371 for (RemoteConference c : mConferenceById.values()) { 372 c.setDestroyed(); 373 } 374 mConnectionById.clear(); 375 mConferenceById.clear(); 376 mPendingConnections.clear(); 377 mOutgoingConnectionServiceRpc.asBinder().unlinkToDeath(mDeathRecipient, 0); 378 } 379 }; 380 381 private final IConnectionService mOutgoingConnectionServiceRpc; 382 private final ConnectionService mOurConnectionServiceImpl; 383 private final Map<String, RemoteConnection> mConnectionById = new HashMap<>(); 384 private final Map<String, RemoteConference> mConferenceById = new HashMap<>(); 385 private final Set<RemoteConnection> mPendingConnections = new HashSet<>(); 386 387 RemoteConnectionService( 388 IConnectionService outgoingConnectionServiceRpc, 389 ConnectionService ourConnectionServiceImpl) throws RemoteException { 390 mOutgoingConnectionServiceRpc = outgoingConnectionServiceRpc; 391 mOutgoingConnectionServiceRpc.asBinder().linkToDeath(mDeathRecipient, 0); 392 mOurConnectionServiceImpl = ourConnectionServiceImpl; 393 } 394 395 @Override 396 public String toString() { 397 return "[RemoteCS - " + mOutgoingConnectionServiceRpc.asBinder().toString() + "]"; 398 } 399 400 final RemoteConnection createRemoteConnection( 401 PhoneAccountHandle connectionManagerPhoneAccount, 402 ConnectionRequest request, 403 boolean isIncoming) { 404 final String id = UUID.randomUUID().toString(); 405 final ConnectionRequest newRequest = new ConnectionRequest( 406 request.getAccountHandle(), 407 request.getAddress(), 408 request.getExtras(), 409 request.getVideoState()); 410 try { 411 if (mConnectionById.isEmpty()) { 412 mOutgoingConnectionServiceRpc.addConnectionServiceAdapter(mServant.getStub()); 413 } 414 RemoteConnection connection = 415 new RemoteConnection(id, mOutgoingConnectionServiceRpc, newRequest); 416 mPendingConnections.add(connection); 417 mConnectionById.put(id, connection); 418 mOutgoingConnectionServiceRpc.createConnection( 419 connectionManagerPhoneAccount, 420 id, 421 newRequest, 422 isIncoming, 423 false /* isUnknownCall */); 424 connection.registerCallback(new RemoteConnection.Callback() { 425 @Override 426 public void onDestroyed(RemoteConnection connection) { 427 mConnectionById.remove(id); 428 maybeDisconnectAdapter(); 429 } 430 }); 431 return connection; 432 } catch (RemoteException e) { 433 return RemoteConnection.failure( 434 new DisconnectCause(DisconnectCause.ERROR, e.toString())); 435 } 436 } 437 438 private boolean hasConnection(String callId) { 439 return mConnectionById.containsKey(callId); 440 } 441 442 private RemoteConnection findConnectionForAction( 443 String callId, String action) { 444 if (mConnectionById.containsKey(callId)) { 445 return mConnectionById.get(callId); 446 } 447 Log.w(this, "%s - Cannot find Connection %s", action, callId); 448 return NULL_CONNECTION; 449 } 450 451 private RemoteConference findConferenceForAction( 452 String callId, String action) { 453 if (mConferenceById.containsKey(callId)) { 454 return mConferenceById.get(callId); 455 } 456 Log.w(this, "%s - Cannot find Conference %s", action, callId); 457 return NULL_CONFERENCE; 458 } 459 460 private void maybeDisconnectAdapter() { 461 if (mConnectionById.isEmpty() && mConferenceById.isEmpty()) { 462 try { 463 mOutgoingConnectionServiceRpc.removeConnectionServiceAdapter(mServant.getStub()); 464 } catch (RemoteException e) { 465 } 466 } 467 } 468} 469