• 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.List;
35 import java.util.Map;
36 import java.util.Set;
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 handleCreateConferenceComplete(
105                 String id,
106                 ConnectionRequest request,
107                 ParcelableConference parcel,
108                 Session.Info info) {
109         }
110 
111         @Override
112         public void setActive(String callId, Session.Info sessionInfo) {
113             if (mConnectionById.containsKey(callId)) {
114                 findConnectionForAction(callId, "setActive")
115                         .setState(Connection.STATE_ACTIVE);
116             } else {
117                 findConferenceForAction(callId, "setActive")
118                         .setState(Connection.STATE_ACTIVE);
119             }
120         }
121 
122         @Override
123         public void setRinging(String callId, Session.Info sessionInfo) {
124             findConnectionForAction(callId, "setRinging")
125                     .setState(Connection.STATE_RINGING);
126         }
127 
128         @Override
129         public void setDialing(String callId, Session.Info sessionInfo) {
130             findConnectionForAction(callId, "setDialing")
131                     .setState(Connection.STATE_DIALING);
132         }
133 
134         @Override
135         public void setPulling(String callId, Session.Info sessionInfo) {
136             findConnectionForAction(callId, "setPulling")
137                     .setState(Connection.STATE_PULLING_CALL);
138         }
139 
140         @Override
141         public void setDisconnected(String callId, DisconnectCause disconnectCause,
142                 Session.Info sessionInfo) {
143             if (mConnectionById.containsKey(callId)) {
144                 findConnectionForAction(callId, "setDisconnected")
145                         .setDisconnected(disconnectCause);
146             } else {
147                 findConferenceForAction(callId, "setDisconnected")
148                         .setDisconnected(disconnectCause);
149             }
150         }
151 
152         @Override
153         public void setOnHold(String callId, Session.Info sessionInfo) {
154             if (mConnectionById.containsKey(callId)) {
155                 findConnectionForAction(callId, "setOnHold")
156                         .setState(Connection.STATE_HOLDING);
157             } else {
158                 findConferenceForAction(callId, "setOnHold")
159                         .setState(Connection.STATE_HOLDING);
160             }
161         }
162 
163         @Override
164         public void setRingbackRequested(String callId, boolean ringing, Session.Info sessionInfo) {
165             findConnectionForAction(callId, "setRingbackRequested")
166                     .setRingbackRequested(ringing);
167         }
168 
169         @Override
170         public void setConnectionCapabilities(String callId, int connectionCapabilities,
171                 Session.Info sessionInfo) {
172             if (mConnectionById.containsKey(callId)) {
173                 findConnectionForAction(callId, "setConnectionCapabilities")
174                         .setConnectionCapabilities(connectionCapabilities);
175             } else {
176                 findConferenceForAction(callId, "setConnectionCapabilities")
177                         .setConnectionCapabilities(connectionCapabilities);
178             }
179         }
180 
181         @Override
182         public void setConnectionProperties(String callId, int connectionProperties,
183                 Session.Info sessionInfo) {
184             if (mConnectionById.containsKey(callId)) {
185                 findConnectionForAction(callId, "setConnectionProperties")
186                         .setConnectionProperties(connectionProperties);
187             } else {
188                 findConferenceForAction(callId, "setConnectionProperties")
189                         .setConnectionProperties(connectionProperties);
190             }
191         }
192 
193         @Override
194         public void setIsConferenced(String callId, String conferenceCallId,
195                 Session.Info sessionInfo) {
196             // Note: callId should not be null; conferenceCallId may be null
197             RemoteConnection connection =
198                     findConnectionForAction(callId, "setIsConferenced");
199             if (connection != NULL_CONNECTION) {
200                 if (conferenceCallId == null) {
201                     // 'connection' is being split from its conference
202                     if (connection.getConference() != null) {
203                         connection.getConference().removeConnection(connection);
204                     }
205                 } else {
206                     RemoteConference conference =
207                             findConferenceForAction(conferenceCallId, "setIsConferenced");
208                     if (conference != NULL_CONFERENCE) {
209                         conference.addConnection(connection);
210                     }
211                 }
212             }
213         }
214 
215         @Override
216         public void setConferenceMergeFailed(String callId, Session.Info sessionInfo) {
217             // Nothing to do here.
218             // The event has already been handled and there is no state to update
219             // in the underlying connection or conference objects
220         }
221 
222         @Override
223         public void onPhoneAccountChanged(String callId, PhoneAccountHandle pHandle,
224                 Session.Info sessionInfo) {
225         }
226 
227         @Override
228         public void onConnectionServiceFocusReleased(Session.Info sessionInfo) {}
229 
230         @Override
231         public void addConferenceCall(
232                 final String callId, ParcelableConference parcel, Session.Info sessionInfo) {
233             RemoteConference conference = new RemoteConference(callId,
234                     mOutgoingConnectionServiceRpc);
235 
236             for (String id : parcel.getConnectionIds()) {
237                 RemoteConnection c = mConnectionById.get(id);
238                 if (c != null) {
239                     conference.addConnection(c);
240                 }
241             }
242             // We used to skip adding empty conferences; however in the world of IMS conference
243             // calls we need to add them to the remote connection service because they will always
244             // start with no participants.
245 
246             conference.setState(parcel.getState());
247             conference.setConnectionCapabilities(parcel.getConnectionCapabilities());
248             conference.setConnectionProperties(parcel.getConnectionProperties());
249             conference.putExtras(parcel.getExtras());
250             mConferenceById.put(callId, conference);
251 
252             // Stash the original connection ID as it exists in the source ConnectionService.
253             // Telecom will use this to avoid adding duplicates later.
254             // See comments on Connection.EXTRA_ORIGINAL_CONNECTION_ID for more information.
255             Bundle newExtras = new Bundle();
256             newExtras.putString(Connection.EXTRA_ORIGINAL_CONNECTION_ID, callId);
257             // Track the fact this request was relayed through the remote connection service.
258             newExtras.putParcelable(Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE,
259                     parcel.getPhoneAccount());
260             conference.putExtras(newExtras);
261 
262             conference.registerCallback(new RemoteConference.Callback() {
263                 @Override
264                 public void onDestroyed(RemoteConference c) {
265                     mConferenceById.remove(callId);
266                     maybeDisconnectAdapter();
267                 }
268             });
269 
270             mOurConnectionServiceImpl.addRemoteConference(conference);
271         }
272 
273         @Override
274         public void removeCall(String callId, Session.Info sessionInfo) {
275             if (mConnectionById.containsKey(callId)) {
276                 findConnectionForAction(callId, "removeCall")
277                         .setDestroyed();
278             } else {
279                 findConferenceForAction(callId, "removeCall")
280                         .setDestroyed();
281             }
282         }
283 
284         @Override
285         public void onPostDialWait(String callId, String remaining, Session.Info sessionInfo) {
286             findConnectionForAction(callId, "onPostDialWait")
287                     .setPostDialWait(remaining);
288         }
289 
290         @Override
291         public void onPostDialChar(String callId, char nextChar, Session.Info sessionInfo) {
292             findConnectionForAction(callId, "onPostDialChar")
293                     .onPostDialChar(nextChar);
294         }
295 
296         @Override
297         public void queryRemoteConnectionServices(RemoteServiceCallback callback,
298                 String callingPackage, Session.Info sessionInfo) {
299             // Not supported from remote connection service.
300         }
301 
302         @Override
303         public void setVideoProvider(String callId, IVideoProvider videoProvider,
304                 Session.Info sessionInfo) {
305 
306             String callingPackage = mOurConnectionServiceImpl.getApplicationContext()
307                     .getOpPackageName();
308             int targetSdkVersion = mOurConnectionServiceImpl.getApplicationInfo().targetSdkVersion;
309             RemoteConnection.VideoProvider remoteVideoProvider = null;
310             if (videoProvider != null) {
311                 remoteVideoProvider = new RemoteConnection.VideoProvider(videoProvider,
312                         callingPackage, targetSdkVersion);
313             }
314             findConnectionForAction(callId, "setVideoProvider")
315                     .setVideoProvider(remoteVideoProvider);
316         }
317 
318         @Override
319         public void setVideoState(String callId, int videoState, Session.Info sessionInfo) {
320             findConnectionForAction(callId, "setVideoState")
321                     .setVideoState(videoState);
322         }
323 
324         @Override
325         public void setIsVoipAudioMode(String callId, boolean isVoip, Session.Info sessionInfo) {
326             findConnectionForAction(callId, "setIsVoipAudioMode")
327                     .setIsVoipAudioMode(isVoip);
328         }
329 
330         @Override
331         public void setStatusHints(String callId, StatusHints statusHints,
332                 Session.Info sessionInfo) {
333             findConnectionForAction(callId, "setStatusHints")
334                     .setStatusHints(statusHints);
335         }
336 
337         @Override
338         public void setAddress(String callId, Uri address, int presentation,
339                 Session.Info sessionInfo) {
340             findConnectionForAction(callId, "setAddress")
341                     .setAddress(address, presentation);
342         }
343 
344         @Override
345         public void setCallerDisplayName(String callId, String callerDisplayName,
346                 int presentation, Session.Info sessionInfo) {
347             findConnectionForAction(callId, "setCallerDisplayName")
348                     .setCallerDisplayName(callerDisplayName, presentation);
349         }
350 
351         @Override
352         public IBinder asBinder() {
353             throw new UnsupportedOperationException();
354         }
355 
356         @Override
357         public final void setConferenceableConnections(String callId,
358                 List<String> conferenceableConnectionIds, Session.Info sessionInfo) {
359             List<RemoteConnection> conferenceable = new ArrayList<>();
360             for (String id : conferenceableConnectionIds) {
361                 if (mConnectionById.containsKey(id)) {
362                     conferenceable.add(mConnectionById.get(id));
363                 }
364             }
365 
366             if (hasConnection(callId)) {
367                 findConnectionForAction(callId, "setConferenceableConnections")
368                         .setConferenceableConnections(conferenceable);
369             } else {
370                 findConferenceForAction(callId, "setConferenceableConnections")
371                         .setConferenceableConnections(conferenceable);
372             }
373         }
374 
375         @Override
376         public void addExistingConnection(String callId, ParcelableConnection connection,
377                 Session.Info sessionInfo) {
378             Log.i(RemoteConnectionService.this, "addExistingConnection: callId=%s, conn=%s", callId,
379                     connection);
380             String callingPackage = mOurConnectionServiceImpl.getApplicationContext().
381                     getOpPackageName();
382             int callingTargetSdkVersion = mOurConnectionServiceImpl.getApplicationInfo()
383                     .targetSdkVersion;
384             RemoteConnection remoteConnection = new RemoteConnection(callId,
385                     mOutgoingConnectionServiceRpc, connection, callingPackage,
386                     callingTargetSdkVersion);
387             // Track that it is via a remote connection.
388             Bundle newExtras = new Bundle();
389             newExtras.putParcelable(Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE,
390                     connection.getPhoneAccount());
391             if (connection.getParentCallId() != null) {
392                 RemoteConference parentConf = mConferenceById.get(connection.getParentCallId());
393                 // If there is a parent being set, we need to stash the conference ID here.
394                 // Telephony can add an existing connection while specifying a parent conference.
395                 // There is no equivalent version of that operation as part of the remote connection
396                 // API, so we will stash the pre-defined parent's ID in the extras.  When the
397                 // connectionmanager copies over the extras from the remote connection to the
398                 // actual one, it'll get passed to Telecom so that it can make the association.
399                 if (parentConf != null) {
400                     newExtras.putString(Connection.EXTRA_ADD_TO_CONFERENCE_ID, parentConf.getId());
401                     Log.i(this, "addExistingConnection: stash parent of %s as %s",
402                             connection.getParentCallId(), parentConf.getId());
403                 }
404             }
405             remoteConnection.putExtras(newExtras);
406             mConnectionById.put(callId, remoteConnection);
407             remoteConnection.registerCallback(new RemoteConnection.Callback() {
408                 @Override
409                 public void onDestroyed(RemoteConnection connection) {
410                     mConnectionById.remove(callId);
411                     maybeDisconnectAdapter();
412                 }
413             });
414             mOurConnectionServiceImpl.addRemoteExistingConnection(remoteConnection);
415         }
416 
417         @Override
418         public void putExtras(String callId, Bundle extras, Session.Info sessionInfo) {
419             if (hasConnection(callId)) {
420                 findConnectionForAction(callId, "putExtras").putExtras(extras);
421             } else {
422                 findConferenceForAction(callId, "putExtras").putExtras(extras);
423             }
424         }
425 
426         @Override
427         public void removeExtras(String callId, List<String> keys, Session.Info sessionInfo) {
428             if (hasConnection(callId)) {
429                 findConnectionForAction(callId, "removeExtra").removeExtras(keys);
430             } else {
431                 findConferenceForAction(callId, "removeExtra").removeExtras(keys);
432             }
433         }
434 
435         @Override
436         public void setAudioRoute(String callId, int audioRoute, String bluetoothAddress,
437                 Session.Info sessionInfo) {
438             if (hasConnection(callId)) {
439                 // TODO(3pcalls): handle this for remote connections.
440                 // Likely we don't want to do anything since it doesn't make sense for self-managed
441                 // connections to go through a connection mgr.
442             }
443         }
444 
445         @Override
446         public void onConnectionEvent(String callId, String event, Bundle extras,
447                 Session.Info sessionInfo) {
448             if (mConnectionById.containsKey(callId)) {
449                 findConnectionForAction(callId, "onConnectionEvent").onConnectionEvent(event,
450                         extras);
451             }
452         }
453 
454         @Override
455         public void onRttInitiationSuccess(String callId, Session.Info sessionInfo)
456                 throws RemoteException {
457             if (hasConnection(callId)) {
458                 findConnectionForAction(callId, "onRttInitiationSuccess")
459                         .onRttInitiationSuccess();
460             } else {
461                 Log.w(this, "onRttInitiationSuccess called on a remote conference");
462             }
463         }
464 
465         @Override
466         public void onRttInitiationFailure(String callId, int reason, Session.Info sessionInfo)
467                 throws RemoteException {
468             if (hasConnection(callId)) {
469                 findConnectionForAction(callId, "onRttInitiationFailure")
470                         .onRttInitiationFailure(reason);
471             } else {
472                 Log.w(this, "onRttInitiationFailure called on a remote conference");
473             }
474         }
475 
476         @Override
477         public void onRttSessionRemotelyTerminated(String callId, Session.Info sessionInfo)
478                 throws RemoteException {
479             if (hasConnection(callId)) {
480                 findConnectionForAction(callId, "onRttSessionRemotelyTerminated")
481                         .onRttSessionRemotelyTerminated();
482             } else {
483                 Log.w(this, "onRttSessionRemotelyTerminated called on a remote conference");
484             }
485         }
486 
487         @Override
488         public void onRemoteRttRequest(String callId, Session.Info sessionInfo)
489                 throws RemoteException {
490             if (hasConnection(callId)) {
491                 findConnectionForAction(callId, "onRemoteRttRequest")
492                         .onRemoteRttRequest();
493             } else {
494                 Log.w(this, "onRemoteRttRequest called on a remote conference");
495             }
496         }
497 
498         @Override
499         public void resetConnectionTime(String callId, Session.Info sessionInfo) {
500             // Do nothing
501         }
502 
503         @Override
504         public void setConferenceState(String callId, boolean isConference,
505                 Session.Info sessionInfo) {
506             // Do nothing
507         }
508 
509         @Override
510         public void setCallDirection(String callId, int direction, Session.Info sessionInfo) {
511             // Do nothing
512         }
513     };
514 
515     private final ConnectionServiceAdapterServant mServant =
516             new ConnectionServiceAdapterServant(mServantDelegate);
517 
518     private final DeathRecipient mDeathRecipient = new DeathRecipient() {
519         @Override
520         public void binderDied() {
521             for (RemoteConnection c : mConnectionById.values()) {
522                 c.setDestroyed();
523             }
524             for (RemoteConference c : mConferenceById.values()) {
525                 c.setDestroyed();
526             }
527             mConnectionById.clear();
528             mConferenceById.clear();
529             mPendingConnections.clear();
530             mOutgoingConnectionServiceRpc.asBinder().unlinkToDeath(mDeathRecipient, 0);
531         }
532     };
533 
534     private final IConnectionService mOutgoingConnectionServiceRpc;
535     private final ConnectionService mOurConnectionServiceImpl;
536     private final Map<String, RemoteConnection> mConnectionById = new HashMap<>();
537     private final Map<String, RemoteConference> mConferenceById = new HashMap<>();
538     private final Set<RemoteConnection> mPendingConnections = new HashSet<>();
539 
RemoteConnectionService( IConnectionService outgoingConnectionServiceRpc, ConnectionService ourConnectionServiceImpl)540     RemoteConnectionService(
541             IConnectionService outgoingConnectionServiceRpc,
542             ConnectionService ourConnectionServiceImpl) throws RemoteException {
543         mOutgoingConnectionServiceRpc = outgoingConnectionServiceRpc;
544         mOutgoingConnectionServiceRpc.asBinder().linkToDeath(mDeathRecipient, 0);
545         mOurConnectionServiceImpl = ourConnectionServiceImpl;
546     }
547 
548     @Override
toString()549     public String toString() {
550         return "[RemoteCS - " + mOutgoingConnectionServiceRpc.asBinder().toString() + "]";
551     }
552 
createRemoteConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request, boolean isIncoming)553     final RemoteConnection createRemoteConnection(
554             PhoneAccountHandle connectionManagerPhoneAccount,
555             ConnectionRequest request,
556             boolean isIncoming) {
557         final String id = UUID.randomUUID().toString();
558         Bundle extras = new Bundle();
559         if (request.getExtras() != null) {
560             extras.putAll(request.getExtras());
561         }
562         // We will set the package name for the originator of the remote request; this lets the
563         // receiving ConnectionService know that the request originated from a remote connection
564         // service so that it can provide tracking information for Telecom.
565         extras.putString(Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME,
566                 mOurConnectionServiceImpl.getApplicationContext().getOpPackageName());
567 
568         final ConnectionRequest newRequest = new ConnectionRequest.Builder()
569                 .setAccountHandle(request.getAccountHandle())
570                 .setAddress(request.getAddress())
571                 .setExtras(extras)
572                 .setVideoState(request.getVideoState())
573                 .setRttPipeFromInCall(request.getRttPipeFromInCall())
574                 .setRttPipeToInCall(request.getRttPipeToInCall())
575                 .build();
576         try {
577             if (mConnectionById.isEmpty()) {
578                 mOutgoingConnectionServiceRpc.addConnectionServiceAdapter(mServant.getStub(),
579                         null /*Session.Info*/);
580             }
581             RemoteConnection connection =
582                     new RemoteConnection(id, mOutgoingConnectionServiceRpc, newRequest);
583             mPendingConnections.add(connection);
584             mConnectionById.put(id, connection);
585             mOutgoingConnectionServiceRpc.createConnection(
586                     connectionManagerPhoneAccount,
587                     id,
588                     newRequest,
589                     isIncoming,
590                     false /* isUnknownCall */,
591                     null /*Session.info*/);
592             connection.registerCallback(new RemoteConnection.Callback() {
593                 @Override
594                 public void onDestroyed(RemoteConnection connection) {
595                     mConnectionById.remove(id);
596                     maybeDisconnectAdapter();
597                 }
598             });
599             return connection;
600         } catch (RemoteException e) {
601             return RemoteConnection.failure(
602                     new DisconnectCause(DisconnectCause.ERROR, e.toString()));
603         }
604     }
605 
createRemoteConference( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request, boolean isIncoming)606     RemoteConference createRemoteConference(
607             PhoneAccountHandle connectionManagerPhoneAccount,
608             ConnectionRequest request,
609             boolean isIncoming) {
610         final String id = UUID.randomUUID().toString();
611         try {
612             if (mConferenceById.isEmpty()) {
613                 mOutgoingConnectionServiceRpc.addConnectionServiceAdapter(mServant.getStub(),
614                         null /*Session.Info*/);
615             }
616             RemoteConference conference = new RemoteConference(id, mOutgoingConnectionServiceRpc);
617             mOutgoingConnectionServiceRpc.createConference(connectionManagerPhoneAccount,
618                     id,
619                     request,
620                     isIncoming,
621                     false /* isUnknownCall */,
622                     null /*Session.info*/);
623             conference.registerCallback(new RemoteConference.Callback() {
624                 @Override
625                 public void onDestroyed(RemoteConference conference) {
626                     mConferenceById.remove(id);
627                     maybeDisconnectAdapter();
628                 }
629             });
630             conference.putExtras(request.getExtras());
631             return conference;
632         } catch (RemoteException e) {
633             return RemoteConference.failure(
634                     new DisconnectCause(DisconnectCause.ERROR, e.toString()));
635         }
636     }
637 
hasConnection(String callId)638     private boolean hasConnection(String callId) {
639         return mConnectionById.containsKey(callId);
640     }
641 
findConnectionForAction( String callId, String action)642     private RemoteConnection findConnectionForAction(
643             String callId, String action) {
644         if (mConnectionById.containsKey(callId)) {
645             return mConnectionById.get(callId);
646         }
647         Log.w(this, "%s - Cannot find Connection %s", action, callId);
648         return NULL_CONNECTION;
649     }
650 
findConferenceForAction( String callId, String action)651     private RemoteConference findConferenceForAction(
652             String callId, String action) {
653         if (mConferenceById.containsKey(callId)) {
654             return mConferenceById.get(callId);
655         }
656         Log.w(this, "%s - Cannot find Conference %s", action, callId);
657         return NULL_CONFERENCE;
658     }
659 
maybeDisconnectAdapter()660     private void maybeDisconnectAdapter() {
661         if (mConnectionById.isEmpty() && mConferenceById.isEmpty()) {
662             try {
663                 mOutgoingConnectionServiceRpc.removeConnectionServiceAdapter(mServant.getStub(),
664                         null /*Session.info*/);
665             } catch (RemoteException e) {
666             }
667         }
668     }
669 }
670