• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 import android.telecom.Logging.Session;
25 
26 import com.android.internal.telecom.IConnectionService;
27 import com.android.internal.telecom.IConnectionServiceAdapter;
28 import com.android.internal.telecom.IVideoProvider;
29 import com.android.internal.telecom.RemoteServiceCallback;
30 
31 import java.util.ArrayList;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.Map;
35 import java.util.Set;
36 import java.util.List;
37 import 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  */
44 final 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 
RemoteConnectionService( IConnectionService outgoingConnectionServiceRpc, ConnectionService ourConnectionServiceImpl)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
toString()505     public String toString() {
506         return "[RemoteCS - " + mOutgoingConnectionServiceRpc.asBinder().toString() + "]";
507     }
508 
createRemoteConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request, boolean isIncoming)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 
hasConnection(String callId)552     private boolean hasConnection(String callId) {
553         return mConnectionById.containsKey(callId);
554     }
555 
findConnectionForAction( String callId, String action)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 
findConferenceForAction( String callId, String action)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 
maybeDisconnectAdapter()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