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