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