• 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             }
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 
RemoteConnectionService( IConnectionService outgoingConnectionServiceRpc, ConnectionService ourConnectionServiceImpl)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
toString()492     public String toString() {
493         return "[RemoteCS - " + mOutgoingConnectionServiceRpc.asBinder().toString() + "]";
494     }
495 
createRemoteConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request, boolean isIncoming)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 
hasConnection(String callId)539     private boolean hasConnection(String callId) {
540         return mConnectionById.containsKey(callId);
541     }
542 
findConnectionForAction( String callId, String action)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 
findConferenceForAction( String callId, String action)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 
maybeDisconnectAdapter()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