• 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.annotation.SdkConstant;
20 import android.app.Service;
21 import android.content.ComponentName;
22 import android.content.Intent;
23 import android.net.Uri;
24 import android.os.Bundle;
25 import android.os.Handler;
26 import android.os.IBinder;
27 import android.os.Looper;
28 import android.os.Message;
29 
30 import com.android.internal.os.SomeArgs;
31 import com.android.internal.telecom.IConnectionService;
32 import com.android.internal.telecom.IConnectionServiceAdapter;
33 import com.android.internal.telecom.RemoteServiceCallback;
34 
35 import java.util.ArrayList;
36 import java.util.Collection;
37 import java.util.Collections;
38 import java.util.List;
39 import java.util.Map;
40 import java.util.UUID;
41 import java.util.concurrent.ConcurrentHashMap;
42 
43 /**
44  * An abstract service that should be implemented by any apps which can make phone calls (VoIP or
45  * otherwise) and want those calls to be integrated into the built-in phone app.
46  * Once implemented, the {@code ConnectionService} needs two additional steps before it will be
47  * integrated into the phone app:
48  * <p>
49  * 1. <i>Registration in AndroidManifest.xml</i>
50  * <br/>
51  * <pre>
52  * &lt;service android:name="com.example.package.MyConnectionService"
53  *    android:label="@string/some_label_for_my_connection_service"
54  *    android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"&gt;
55  *  &lt;intent-filter&gt;
56  *   &lt;action android:name="android.telecom.ConnectionService" /&gt;
57  *  &lt;/intent-filter&gt;
58  * &lt;/service&gt;
59  * </pre>
60  * <p>
61  * 2. <i> Registration of {@link PhoneAccount} with {@link TelecomManager}.</i>
62  * <br/>
63  * See {@link PhoneAccount} and {@link TelecomManager#registerPhoneAccount} for more information.
64  * <p>
65  * Once registered and enabled by the user in the phone app settings, telecom will bind to a
66  * {@code ConnectionService} implementation when it wants that {@code ConnectionService} to place
67  * a call or the service has indicated that is has an incoming call through
68  * {@link TelecomManager#addNewIncomingCall}. The {@code ConnectionService} can then expect a call
69  * to {@link #onCreateIncomingConnection} or {@link #onCreateOutgoingConnection} wherein it
70  * should provide a new instance of a {@link Connection} object.  It is through this
71  * {@link Connection} object that telecom receives state updates and the {@code ConnectionService}
72  * receives call-commands such as answer, reject, hold and disconnect.
73  * <p>
74  * When there are no more live calls, telecom will unbind from the {@code ConnectionService}.
75  */
76 public abstract class ConnectionService extends Service {
77     /**
78      * The {@link Intent} that must be declared as handled by the service.
79      */
80     @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
81     public static final String SERVICE_INTERFACE = "android.telecom.ConnectionService";
82 
83     // Flag controlling whether PII is emitted into the logs
84     private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG);
85 
86     private static final int MSG_ADD_CONNECTION_SERVICE_ADAPTER = 1;
87     private static final int MSG_CREATE_CONNECTION = 2;
88     private static final int MSG_ABORT = 3;
89     private static final int MSG_ANSWER = 4;
90     private static final int MSG_REJECT = 5;
91     private static final int MSG_DISCONNECT = 6;
92     private static final int MSG_HOLD = 7;
93     private static final int MSG_UNHOLD = 8;
94     private static final int MSG_ON_CALL_AUDIO_STATE_CHANGED = 9;
95     private static final int MSG_PLAY_DTMF_TONE = 10;
96     private static final int MSG_STOP_DTMF_TONE = 11;
97     private static final int MSG_CONFERENCE = 12;
98     private static final int MSG_SPLIT_FROM_CONFERENCE = 13;
99     private static final int MSG_ON_POST_DIAL_CONTINUE = 14;
100     private static final int MSG_REMOVE_CONNECTION_SERVICE_ADAPTER = 16;
101     private static final int MSG_ANSWER_VIDEO = 17;
102     private static final int MSG_MERGE_CONFERENCE = 18;
103     private static final int MSG_SWAP_CONFERENCE = 19;
104 
105     private static Connection sNullConnection;
106 
107     private final Map<String, Connection> mConnectionById = new ConcurrentHashMap<>();
108     private final Map<Connection, String> mIdByConnection = new ConcurrentHashMap<>();
109     private final Map<String, Conference> mConferenceById = new ConcurrentHashMap<>();
110     private final Map<Conference, String> mIdByConference = new ConcurrentHashMap<>();
111     private final RemoteConnectionManager mRemoteConnectionManager =
112             new RemoteConnectionManager(this);
113     private final List<Runnable> mPreInitializationConnectionRequests = new ArrayList<>();
114     private final ConnectionServiceAdapter mAdapter = new ConnectionServiceAdapter();
115 
116     private boolean mAreAccountsInitialized = false;
117     private Conference sNullConference;
118 
119     private final IBinder mBinder = new IConnectionService.Stub() {
120         @Override
121         public void addConnectionServiceAdapter(IConnectionServiceAdapter adapter) {
122             mHandler.obtainMessage(MSG_ADD_CONNECTION_SERVICE_ADAPTER, adapter).sendToTarget();
123         }
124 
125         public void removeConnectionServiceAdapter(IConnectionServiceAdapter adapter) {
126             mHandler.obtainMessage(MSG_REMOVE_CONNECTION_SERVICE_ADAPTER, adapter).sendToTarget();
127         }
128 
129         @Override
130         public void createConnection(
131                 PhoneAccountHandle connectionManagerPhoneAccount,
132                 String id,
133                 ConnectionRequest request,
134                 boolean isIncoming,
135                 boolean isUnknown) {
136             SomeArgs args = SomeArgs.obtain();
137             args.arg1 = connectionManagerPhoneAccount;
138             args.arg2 = id;
139             args.arg3 = request;
140             args.argi1 = isIncoming ? 1 : 0;
141             args.argi2 = isUnknown ? 1 : 0;
142             mHandler.obtainMessage(MSG_CREATE_CONNECTION, args).sendToTarget();
143         }
144 
145         @Override
146         public void abort(String callId) {
147             mHandler.obtainMessage(MSG_ABORT, callId).sendToTarget();
148         }
149 
150         @Override
151         public void answerVideo(String callId, int videoState) {
152             SomeArgs args = SomeArgs.obtain();
153             args.arg1 = callId;
154             args.argi1 = videoState;
155             mHandler.obtainMessage(MSG_ANSWER_VIDEO, args).sendToTarget();
156         }
157 
158         @Override
159         public void answer(String callId) {
160             mHandler.obtainMessage(MSG_ANSWER, callId).sendToTarget();
161         }
162 
163         @Override
164         public void reject(String callId) {
165             mHandler.obtainMessage(MSG_REJECT, callId).sendToTarget();
166         }
167 
168         @Override
169         public void disconnect(String callId) {
170             mHandler.obtainMessage(MSG_DISCONNECT, callId).sendToTarget();
171         }
172 
173         @Override
174         public void hold(String callId) {
175             mHandler.obtainMessage(MSG_HOLD, callId).sendToTarget();
176         }
177 
178         @Override
179         public void unhold(String callId) {
180             mHandler.obtainMessage(MSG_UNHOLD, callId).sendToTarget();
181         }
182 
183         @Override
184         public void onCallAudioStateChanged(String callId, CallAudioState callAudioState) {
185             SomeArgs args = SomeArgs.obtain();
186             args.arg1 = callId;
187             args.arg2 = callAudioState;
188             mHandler.obtainMessage(MSG_ON_CALL_AUDIO_STATE_CHANGED, args).sendToTarget();
189         }
190 
191         @Override
192         public void playDtmfTone(String callId, char digit) {
193             mHandler.obtainMessage(MSG_PLAY_DTMF_TONE, digit, 0, callId).sendToTarget();
194         }
195 
196         @Override
197         public void stopDtmfTone(String callId) {
198             mHandler.obtainMessage(MSG_STOP_DTMF_TONE, callId).sendToTarget();
199         }
200 
201         @Override
202         public void conference(String callId1, String callId2) {
203             SomeArgs args = SomeArgs.obtain();
204             args.arg1 = callId1;
205             args.arg2 = callId2;
206             mHandler.obtainMessage(MSG_CONFERENCE, args).sendToTarget();
207         }
208 
209         @Override
210         public void splitFromConference(String callId) {
211             mHandler.obtainMessage(MSG_SPLIT_FROM_CONFERENCE, callId).sendToTarget();
212         }
213 
214         @Override
215         public void mergeConference(String callId) {
216             mHandler.obtainMessage(MSG_MERGE_CONFERENCE, callId).sendToTarget();
217         }
218 
219         @Override
220         public void swapConference(String callId) {
221             mHandler.obtainMessage(MSG_SWAP_CONFERENCE, callId).sendToTarget();
222         }
223 
224         @Override
225         public void onPostDialContinue(String callId, boolean proceed) {
226             SomeArgs args = SomeArgs.obtain();
227             args.arg1 = callId;
228             args.argi1 = proceed ? 1 : 0;
229             mHandler.obtainMessage(MSG_ON_POST_DIAL_CONTINUE, args).sendToTarget();
230         }
231     };
232 
233     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
234         @Override
235         public void handleMessage(Message msg) {
236             switch (msg.what) {
237                 case MSG_ADD_CONNECTION_SERVICE_ADAPTER:
238                     mAdapter.addAdapter((IConnectionServiceAdapter) msg.obj);
239                     onAdapterAttached();
240                     break;
241                 case MSG_REMOVE_CONNECTION_SERVICE_ADAPTER:
242                     mAdapter.removeAdapter((IConnectionServiceAdapter) msg.obj);
243                     break;
244                 case MSG_CREATE_CONNECTION: {
245                     SomeArgs args = (SomeArgs) msg.obj;
246                     try {
247                         final PhoneAccountHandle connectionManagerPhoneAccount =
248                                 (PhoneAccountHandle) args.arg1;
249                         final String id = (String) args.arg2;
250                         final ConnectionRequest request = (ConnectionRequest) args.arg3;
251                         final boolean isIncoming = args.argi1 == 1;
252                         final boolean isUnknown = args.argi2 == 1;
253                         if (!mAreAccountsInitialized) {
254                             Log.d(this, "Enqueueing pre-init request %s", id);
255                             mPreInitializationConnectionRequests.add(new Runnable() {
256                                 @Override
257                                 public void run() {
258                                     createConnection(
259                                             connectionManagerPhoneAccount,
260                                             id,
261                                             request,
262                                             isIncoming,
263                                             isUnknown);
264                                 }
265                             });
266                         } else {
267                             createConnection(
268                                     connectionManagerPhoneAccount,
269                                     id,
270                                     request,
271                                     isIncoming,
272                                     isUnknown);
273                         }
274                     } finally {
275                         args.recycle();
276                     }
277                     break;
278                 }
279                 case MSG_ABORT:
280                     abort((String) msg.obj);
281                     break;
282                 case MSG_ANSWER:
283                     answer((String) msg.obj);
284                     break;
285                 case MSG_ANSWER_VIDEO: {
286                     SomeArgs args = (SomeArgs) msg.obj;
287                     try {
288                         String callId = (String) args.arg1;
289                         int videoState = args.argi1;
290                         answerVideo(callId, videoState);
291                     } finally {
292                         args.recycle();
293                     }
294                     break;
295                 }
296                 case MSG_REJECT:
297                     reject((String) msg.obj);
298                     break;
299                 case MSG_DISCONNECT:
300                     disconnect((String) msg.obj);
301                     break;
302                 case MSG_HOLD:
303                     hold((String) msg.obj);
304                     break;
305                 case MSG_UNHOLD:
306                     unhold((String) msg.obj);
307                     break;
308                 case MSG_ON_CALL_AUDIO_STATE_CHANGED: {
309                     SomeArgs args = (SomeArgs) msg.obj;
310                     try {
311                         String callId = (String) args.arg1;
312                         CallAudioState audioState = (CallAudioState) args.arg2;
313                         onCallAudioStateChanged(callId, new CallAudioState(audioState));
314                     } finally {
315                         args.recycle();
316                     }
317                     break;
318                 }
319                 case MSG_PLAY_DTMF_TONE:
320                     playDtmfTone((String) msg.obj, (char) msg.arg1);
321                     break;
322                 case MSG_STOP_DTMF_TONE:
323                     stopDtmfTone((String) msg.obj);
324                     break;
325                 case MSG_CONFERENCE: {
326                     SomeArgs args = (SomeArgs) msg.obj;
327                     try {
328                         String callId1 = (String) args.arg1;
329                         String callId2 = (String) args.arg2;
330                         conference(callId1, callId2);
331                     } finally {
332                         args.recycle();
333                     }
334                     break;
335                 }
336                 case MSG_SPLIT_FROM_CONFERENCE:
337                     splitFromConference((String) msg.obj);
338                     break;
339                 case MSG_MERGE_CONFERENCE:
340                     mergeConference((String) msg.obj);
341                     break;
342                 case MSG_SWAP_CONFERENCE:
343                     swapConference((String) msg.obj);
344                     break;
345                 case MSG_ON_POST_DIAL_CONTINUE: {
346                     SomeArgs args = (SomeArgs) msg.obj;
347                     try {
348                         String callId = (String) args.arg1;
349                         boolean proceed = (args.argi1 == 1);
350                         onPostDialContinue(callId, proceed);
351                     } finally {
352                         args.recycle();
353                     }
354                     break;
355                 }
356                 default:
357                     break;
358             }
359         }
360     };
361 
362     private final Conference.Listener mConferenceListener = new Conference.Listener() {
363         @Override
364         public void onStateChanged(Conference conference, int oldState, int newState) {
365             String id = mIdByConference.get(conference);
366             switch (newState) {
367                 case Connection.STATE_ACTIVE:
368                     mAdapter.setActive(id);
369                     break;
370                 case Connection.STATE_HOLDING:
371                     mAdapter.setOnHold(id);
372                     break;
373                 case Connection.STATE_DISCONNECTED:
374                     // handled by onDisconnected
375                     break;
376             }
377         }
378 
379         @Override
380         public void onDisconnected(Conference conference, DisconnectCause disconnectCause) {
381             String id = mIdByConference.get(conference);
382             mAdapter.setDisconnected(id, disconnectCause);
383         }
384 
385         @Override
386         public void onConnectionAdded(Conference conference, Connection connection) {
387         }
388 
389         @Override
390         public void onConnectionRemoved(Conference conference, Connection connection) {
391         }
392 
393         @Override
394         public void onConferenceableConnectionsChanged(
395                 Conference conference, List<Connection> conferenceableConnections) {
396             mAdapter.setConferenceableConnections(
397                     mIdByConference.get(conference),
398                     createConnectionIdList(conferenceableConnections));
399         }
400 
401         @Override
402         public void onDestroyed(Conference conference) {
403             removeConference(conference);
404         }
405 
406         @Override
407         public void onConnectionCapabilitiesChanged(
408                 Conference conference,
409                 int connectionCapabilities) {
410             String id = mIdByConference.get(conference);
411             Log.d(this, "call capabilities: conference: %s",
412                     Connection.capabilitiesToString(connectionCapabilities));
413             mAdapter.setConnectionCapabilities(id, connectionCapabilities);
414         }
415 
416         @Override
417         public void onVideoStateChanged(Conference c, int videoState) {
418             String id = mIdByConference.get(c);
419             Log.d(this, "onVideoStateChanged set video state %d", videoState);
420             mAdapter.setVideoState(id, videoState);
421         }
422 
423         @Override
424         public void onVideoProviderChanged(Conference c, Connection.VideoProvider videoProvider) {
425             String id = mIdByConference.get(c);
426             Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c,
427                     videoProvider);
428             mAdapter.setVideoProvider(id, videoProvider);
429         }
430 
431         @Override
432         public void onStatusHintsChanged(Conference conference, StatusHints statusHints) {
433             String id = mIdByConference.get(conference);
434             mAdapter.setStatusHints(id, statusHints);
435         }
436 
437         @Override
438         public void onExtrasChanged(Conference conference, Bundle extras) {
439             String id = mIdByConference.get(conference);
440             mAdapter.setExtras(id, extras);
441         }
442     };
443 
444     private final Connection.Listener mConnectionListener = new Connection.Listener() {
445         @Override
446         public void onStateChanged(Connection c, int state) {
447             String id = mIdByConnection.get(c);
448             Log.d(this, "Adapter set state %s %s", id, Connection.stateToString(state));
449             switch (state) {
450                 case Connection.STATE_ACTIVE:
451                     mAdapter.setActive(id);
452                     break;
453                 case Connection.STATE_DIALING:
454                     mAdapter.setDialing(id);
455                     break;
456                 case Connection.STATE_DISCONNECTED:
457                     // Handled in onDisconnected()
458                     break;
459                 case Connection.STATE_HOLDING:
460                     mAdapter.setOnHold(id);
461                     break;
462                 case Connection.STATE_NEW:
463                     // Nothing to tell Telecom
464                     break;
465                 case Connection.STATE_RINGING:
466                     mAdapter.setRinging(id);
467                     break;
468             }
469         }
470 
471         @Override
472         public void onDisconnected(Connection c, DisconnectCause disconnectCause) {
473             String id = mIdByConnection.get(c);
474             Log.d(this, "Adapter set disconnected %s", disconnectCause);
475             mAdapter.setDisconnected(id, disconnectCause);
476         }
477 
478         @Override
479         public void onVideoStateChanged(Connection c, int videoState) {
480             String id = mIdByConnection.get(c);
481             Log.d(this, "Adapter set video state %d", videoState);
482             mAdapter.setVideoState(id, videoState);
483         }
484 
485         @Override
486         public void onAddressChanged(Connection c, Uri address, int presentation) {
487             String id = mIdByConnection.get(c);
488             mAdapter.setAddress(id, address, presentation);
489         }
490 
491         @Override
492         public void onCallerDisplayNameChanged(
493                 Connection c, String callerDisplayName, int presentation) {
494             String id = mIdByConnection.get(c);
495             mAdapter.setCallerDisplayName(id, callerDisplayName, presentation);
496         }
497 
498         @Override
499         public void onDestroyed(Connection c) {
500             removeConnection(c);
501         }
502 
503         @Override
504         public void onPostDialWait(Connection c, String remaining) {
505             String id = mIdByConnection.get(c);
506             Log.d(this, "Adapter onPostDialWait %s, %s", c, remaining);
507             mAdapter.onPostDialWait(id, remaining);
508         }
509 
510         @Override
511         public void onPostDialChar(Connection c, char nextChar) {
512             String id = mIdByConnection.get(c);
513             Log.d(this, "Adapter onPostDialChar %s, %s", c, nextChar);
514             mAdapter.onPostDialChar(id, nextChar);
515         }
516 
517         @Override
518         public void onRingbackRequested(Connection c, boolean ringback) {
519             String id = mIdByConnection.get(c);
520             Log.d(this, "Adapter onRingback %b", ringback);
521             mAdapter.setRingbackRequested(id, ringback);
522         }
523 
524         @Override
525         public void onConnectionCapabilitiesChanged(Connection c, int capabilities) {
526             String id = mIdByConnection.get(c);
527             Log.d(this, "capabilities: parcelableconnection: %s",
528                     Connection.capabilitiesToString(capabilities));
529             mAdapter.setConnectionCapabilities(id, capabilities);
530         }
531 
532         @Override
533         public void onVideoProviderChanged(Connection c, Connection.VideoProvider videoProvider) {
534             String id = mIdByConnection.get(c);
535             Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c,
536                     videoProvider);
537             mAdapter.setVideoProvider(id, videoProvider);
538         }
539 
540         @Override
541         public void onAudioModeIsVoipChanged(Connection c, boolean isVoip) {
542             String id = mIdByConnection.get(c);
543             mAdapter.setIsVoipAudioMode(id, isVoip);
544         }
545 
546         @Override
547         public void onStatusHintsChanged(Connection c, StatusHints statusHints) {
548             String id = mIdByConnection.get(c);
549             mAdapter.setStatusHints(id, statusHints);
550         }
551 
552         @Override
553         public void onConferenceablesChanged(
554                 Connection connection, List<Conferenceable> conferenceables) {
555             mAdapter.setConferenceableConnections(
556                     mIdByConnection.get(connection),
557                     createIdList(conferenceables));
558         }
559 
560         @Override
561         public void onConferenceChanged(Connection connection, Conference conference) {
562             String id = mIdByConnection.get(connection);
563             if (id != null) {
564                 String conferenceId = null;
565                 if (conference != null) {
566                     conferenceId = mIdByConference.get(conference);
567                 }
568                 mAdapter.setIsConferenced(id, conferenceId);
569             }
570         }
571 
572         @Override
573         public void onConferenceMergeFailed(Connection connection) {
574             String id = mIdByConnection.get(connection);
575             if (id != null) {
576                 mAdapter.onConferenceMergeFailed(id);
577             }
578         }
579 
580         @Override
581         public void onExtrasChanged(Connection connection, Bundle extras) {
582             String id = mIdByConnection.get(connection);
583             if (id != null) {
584                 mAdapter.setExtras(id, extras);
585             }
586         }
587     };
588 
589     /** {@inheritDoc} */
590     @Override
onBind(Intent intent)591     public final IBinder onBind(Intent intent) {
592         return mBinder;
593     }
594 
595     /** {@inheritDoc} */
596     @Override
onUnbind(Intent intent)597     public boolean onUnbind(Intent intent) {
598         endAllConnections();
599         return super.onUnbind(intent);
600     }
601 
602     /**
603      * This can be used by telecom to either create a new outgoing call or attach to an existing
604      * incoming call. In either case, telecom will cycle through a set of services and call
605      * createConnection util a connection service cancels the process or completes it successfully.
606      */
createConnection( final PhoneAccountHandle callManagerAccount, final String callId, final ConnectionRequest request, boolean isIncoming, boolean isUnknown)607     private void createConnection(
608             final PhoneAccountHandle callManagerAccount,
609             final String callId,
610             final ConnectionRequest request,
611             boolean isIncoming,
612             boolean isUnknown) {
613         Log.d(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " +
614                 "isIncoming: %b, isUnknown: %b", callManagerAccount, callId, request, isIncoming,
615                 isUnknown);
616 
617         Connection connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request)
618                 : isIncoming ? onCreateIncomingConnection(callManagerAccount, request)
619                 : onCreateOutgoingConnection(callManagerAccount, request);
620         Log.d(this, "createConnection, connection: %s", connection);
621         if (connection == null) {
622             connection = Connection.createFailedConnection(
623                     new DisconnectCause(DisconnectCause.ERROR));
624         }
625 
626         if (connection.getState() != Connection.STATE_DISCONNECTED) {
627             addConnection(callId, connection);
628         }
629 
630         Uri address = connection.getAddress();
631         String number = address == null ? "null" : address.getSchemeSpecificPart();
632         Log.v(this, "createConnection, number: %s, state: %s, capabilities: %s",
633                 Connection.toLogSafePhoneNumber(number),
634                 Connection.stateToString(connection.getState()),
635                 Connection.capabilitiesToString(connection.getConnectionCapabilities()));
636 
637         Log.d(this, "createConnection, calling handleCreateConnectionSuccessful %s", callId);
638         mAdapter.handleCreateConnectionComplete(
639                 callId,
640                 request,
641                 new ParcelableConnection(
642                         request.getAccountHandle(),
643                         connection.getState(),
644                         connection.getConnectionCapabilities(),
645                         connection.getAddress(),
646                         connection.getAddressPresentation(),
647                         connection.getCallerDisplayName(),
648                         connection.getCallerDisplayNamePresentation(),
649                         connection.getVideoProvider() == null ?
650                                 null : connection.getVideoProvider().getInterface(),
651                         connection.getVideoState(),
652                         connection.isRingbackRequested(),
653                         connection.getAudioModeIsVoip(),
654                         connection.getConnectTimeMillis(),
655                         connection.getStatusHints(),
656                         connection.getDisconnectCause(),
657                         createIdList(connection.getConferenceables()),
658                         connection.getExtras()));
659         if (isUnknown) {
660             triggerConferenceRecalculate();
661         }
662     }
663 
abort(String callId)664     private void abort(String callId) {
665         Log.d(this, "abort %s", callId);
666         findConnectionForAction(callId, "abort").onAbort();
667     }
668 
answerVideo(String callId, int videoState)669     private void answerVideo(String callId, int videoState) {
670         Log.d(this, "answerVideo %s", callId);
671         findConnectionForAction(callId, "answer").onAnswer(videoState);
672     }
673 
answer(String callId)674     private void answer(String callId) {
675         Log.d(this, "answer %s", callId);
676         findConnectionForAction(callId, "answer").onAnswer();
677     }
678 
reject(String callId)679     private void reject(String callId) {
680         Log.d(this, "reject %s", callId);
681         findConnectionForAction(callId, "reject").onReject();
682     }
683 
disconnect(String callId)684     private void disconnect(String callId) {
685         Log.d(this, "disconnect %s", callId);
686         if (mConnectionById.containsKey(callId)) {
687             findConnectionForAction(callId, "disconnect").onDisconnect();
688         } else {
689             findConferenceForAction(callId, "disconnect").onDisconnect();
690         }
691     }
692 
hold(String callId)693     private void hold(String callId) {
694         Log.d(this, "hold %s", callId);
695         if (mConnectionById.containsKey(callId)) {
696             findConnectionForAction(callId, "hold").onHold();
697         } else {
698             findConferenceForAction(callId, "hold").onHold();
699         }
700     }
701 
unhold(String callId)702     private void unhold(String callId) {
703         Log.d(this, "unhold %s", callId);
704         if (mConnectionById.containsKey(callId)) {
705             findConnectionForAction(callId, "unhold").onUnhold();
706         } else {
707             findConferenceForAction(callId, "unhold").onUnhold();
708         }
709     }
710 
onCallAudioStateChanged(String callId, CallAudioState callAudioState)711     private void onCallAudioStateChanged(String callId, CallAudioState callAudioState) {
712         Log.d(this, "onAudioStateChanged %s %s", callId, callAudioState);
713         if (mConnectionById.containsKey(callId)) {
714             findConnectionForAction(callId, "onCallAudioStateChanged").setCallAudioState(
715                     callAudioState);
716         } else {
717             findConferenceForAction(callId, "onCallAudioStateChanged").setCallAudioState(
718                     callAudioState);
719         }
720     }
721 
playDtmfTone(String callId, char digit)722     private void playDtmfTone(String callId, char digit) {
723         Log.d(this, "playDtmfTone %s %c", callId, digit);
724         if (mConnectionById.containsKey(callId)) {
725             findConnectionForAction(callId, "playDtmfTone").onPlayDtmfTone(digit);
726         } else {
727             findConferenceForAction(callId, "playDtmfTone").onPlayDtmfTone(digit);
728         }
729     }
730 
stopDtmfTone(String callId)731     private void stopDtmfTone(String callId) {
732         Log.d(this, "stopDtmfTone %s", callId);
733         if (mConnectionById.containsKey(callId)) {
734             findConnectionForAction(callId, "stopDtmfTone").onStopDtmfTone();
735         } else {
736             findConferenceForAction(callId, "stopDtmfTone").onStopDtmfTone();
737         }
738     }
739 
conference(String callId1, String callId2)740     private void conference(String callId1, String callId2) {
741         Log.d(this, "conference %s, %s", callId1, callId2);
742 
743         // Attempt to get second connection or conference.
744         Connection connection2 = findConnectionForAction(callId2, "conference");
745         Conference conference2 = getNullConference();
746         if (connection2 == getNullConnection()) {
747             conference2 = findConferenceForAction(callId2, "conference");
748             if (conference2 == getNullConference()) {
749                 Log.w(this, "Connection2 or Conference2 missing in conference request %s.",
750                         callId2);
751                 return;
752             }
753         }
754 
755         // Attempt to get first connection or conference and perform merge.
756         Connection connection1 = findConnectionForAction(callId1, "conference");
757         if (connection1 == getNullConnection()) {
758             Conference conference1 = findConferenceForAction(callId1, "addConnection");
759             if (conference1 == getNullConference()) {
760                 Log.w(this,
761                         "Connection1 or Conference1 missing in conference request %s.",
762                         callId1);
763             } else {
764                 // Call 1 is a conference.
765                 if (connection2 != getNullConnection()) {
766                     // Call 2 is a connection so merge via call 1 (conference).
767                     conference1.onMerge(connection2);
768                 } else {
769                     // Call 2 is ALSO a conference; this should never happen.
770                     Log.wtf(this, "There can only be one conference and an attempt was made to " +
771                             "merge two conferences.");
772                     return;
773                 }
774             }
775         } else {
776             // Call 1 is a connection.
777             if (conference2 != getNullConference()) {
778                 // Call 2 is a conference, so merge via call 2.
779                 conference2.onMerge(connection1);
780             } else {
781                 // Call 2 is a connection, so merge together.
782                 onConference(connection1, connection2);
783             }
784         }
785     }
786 
splitFromConference(String callId)787     private void splitFromConference(String callId) {
788         Log.d(this, "splitFromConference(%s)", callId);
789 
790         Connection connection = findConnectionForAction(callId, "splitFromConference");
791         if (connection == getNullConnection()) {
792             Log.w(this, "Connection missing in conference request %s.", callId);
793             return;
794         }
795 
796         Conference conference = connection.getConference();
797         if (conference != null) {
798             conference.onSeparate(connection);
799         }
800     }
801 
mergeConference(String callId)802     private void mergeConference(String callId) {
803         Log.d(this, "mergeConference(%s)", callId);
804         Conference conference = findConferenceForAction(callId, "mergeConference");
805         if (conference != null) {
806             conference.onMerge();
807         }
808     }
809 
swapConference(String callId)810     private void swapConference(String callId) {
811         Log.d(this, "swapConference(%s)", callId);
812         Conference conference = findConferenceForAction(callId, "swapConference");
813         if (conference != null) {
814             conference.onSwap();
815         }
816     }
817 
onPostDialContinue(String callId, boolean proceed)818     private void onPostDialContinue(String callId, boolean proceed) {
819         Log.d(this, "onPostDialContinue(%s)", callId);
820         findConnectionForAction(callId, "stopDtmfTone").onPostDialContinue(proceed);
821     }
822 
onAdapterAttached()823     private void onAdapterAttached() {
824         if (mAreAccountsInitialized) {
825             // No need to query again if we already did it.
826             return;
827         }
828 
829         mAdapter.queryRemoteConnectionServices(new RemoteServiceCallback.Stub() {
830             @Override
831             public void onResult(
832                     final List<ComponentName> componentNames,
833                     final List<IBinder> services) {
834                 mHandler.post(new Runnable() {
835                     @Override
836                     public void run() {
837                         for (int i = 0; i < componentNames.size() && i < services.size(); i++) {
838                             mRemoteConnectionManager.addConnectionService(
839                                     componentNames.get(i),
840                                     IConnectionService.Stub.asInterface(services.get(i)));
841                         }
842                         onAccountsInitialized();
843                         Log.d(this, "remote connection services found: " + services);
844                     }
845                 });
846             }
847 
848             @Override
849             public void onError() {
850                 mHandler.post(new Runnable() {
851                     @Override
852                     public void run() {
853                         mAreAccountsInitialized = true;
854                     }
855                 });
856             }
857         });
858     }
859 
860     /**
861      * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an
862      * incoming request. This is used by {@code ConnectionService}s that are registered with
863      * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} and want to be able to manage
864      * SIM-based incoming calls.
865      *
866      * @param connectionManagerPhoneAccount See description at
867      *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
868      * @param request Details about the incoming call.
869      * @return The {@code Connection} object to satisfy this call, or {@code null} to
870      *         not handle the call.
871      */
createRemoteIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)872     public final RemoteConnection createRemoteIncomingConnection(
873             PhoneAccountHandle connectionManagerPhoneAccount,
874             ConnectionRequest request) {
875         return mRemoteConnectionManager.createRemoteConnection(
876                 connectionManagerPhoneAccount, request, true);
877     }
878 
879     /**
880      * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an
881      * outgoing request. This is used by {@code ConnectionService}s that are registered with
882      * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} and want to be able to use the
883      * SIM-based {@code ConnectionService} to place its outgoing calls.
884      *
885      * @param connectionManagerPhoneAccount See description at
886      *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
887      * @param request Details about the incoming call.
888      * @return The {@code Connection} object to satisfy this call, or {@code null} to
889      *         not handle the call.
890      */
createRemoteOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)891     public final RemoteConnection createRemoteOutgoingConnection(
892             PhoneAccountHandle connectionManagerPhoneAccount,
893             ConnectionRequest request) {
894         return mRemoteConnectionManager.createRemoteConnection(
895                 connectionManagerPhoneAccount, request, false);
896     }
897 
898     /**
899      * Indicates to the relevant {@code RemoteConnectionService} that the specified
900      * {@link RemoteConnection}s should be merged into a conference call.
901      * <p>
902      * If the conference request is successful, the method {@link #onRemoteConferenceAdded} will
903      * be invoked.
904      *
905      * @param remoteConnection1 The first of the remote connections to conference.
906      * @param remoteConnection2 The second of the remote connections to conference.
907      */
conferenceRemoteConnections( RemoteConnection remoteConnection1, RemoteConnection remoteConnection2)908     public final void conferenceRemoteConnections(
909             RemoteConnection remoteConnection1,
910             RemoteConnection remoteConnection2) {
911         mRemoteConnectionManager.conferenceRemoteConnections(remoteConnection1, remoteConnection2);
912     }
913 
914     /**
915      * Adds a new conference call. When a conference call is created either as a result of an
916      * explicit request via {@link #onConference} or otherwise, the connection service should supply
917      * an instance of {@link Conference} by invoking this method. A conference call provided by this
918      * method will persist until {@link Conference#destroy} is invoked on the conference instance.
919      *
920      * @param conference The new conference object.
921      */
addConference(Conference conference)922     public final void addConference(Conference conference) {
923         Log.d(this, "addConference: conference=%s", conference);
924 
925         String id = addConferenceInternal(conference);
926         if (id != null) {
927             List<String> connectionIds = new ArrayList<>(2);
928             for (Connection connection : conference.getConnections()) {
929                 if (mIdByConnection.containsKey(connection)) {
930                     connectionIds.add(mIdByConnection.get(connection));
931                 }
932             }
933             ParcelableConference parcelableConference = new ParcelableConference(
934                     conference.getPhoneAccountHandle(),
935                     conference.getState(),
936                     conference.getConnectionCapabilities(),
937                     connectionIds,
938                     conference.getVideoProvider() == null ?
939                             null : conference.getVideoProvider().getInterface(),
940                     conference.getVideoState(),
941                     conference.getConnectTimeMillis(),
942                     conference.getStatusHints(),
943                     conference.getExtras());
944 
945             mAdapter.addConferenceCall(id, parcelableConference);
946             mAdapter.setVideoProvider(id, conference.getVideoProvider());
947             mAdapter.setVideoState(id, conference.getVideoState());
948 
949             // Go through any child calls and set the parent.
950             for (Connection connection : conference.getConnections()) {
951                 String connectionId = mIdByConnection.get(connection);
952                 if (connectionId != null) {
953                     mAdapter.setIsConferenced(connectionId, id);
954                 }
955             }
956         }
957     }
958 
959     /**
960      * Adds a connection created by the {@link ConnectionService} and informs telecom of the new
961      * connection.
962      *
963      * @param phoneAccountHandle The phone account handle for the connection.
964      * @param connection The connection to add.
965      */
addExistingConnection(PhoneAccountHandle phoneAccountHandle, Connection connection)966     public final void addExistingConnection(PhoneAccountHandle phoneAccountHandle,
967             Connection connection) {
968 
969         String id = addExistingConnectionInternal(connection);
970         if (id != null) {
971             List<String> emptyList = new ArrayList<>(0);
972 
973             ParcelableConnection parcelableConnection = new ParcelableConnection(
974                     phoneAccountHandle,
975                     connection.getState(),
976                     connection.getConnectionCapabilities(),
977                     connection.getAddress(),
978                     connection.getAddressPresentation(),
979                     connection.getCallerDisplayName(),
980                     connection.getCallerDisplayNamePresentation(),
981                     connection.getVideoProvider() == null ?
982                             null : connection.getVideoProvider().getInterface(),
983                     connection.getVideoState(),
984                     connection.isRingbackRequested(),
985                     connection.getAudioModeIsVoip(),
986                     connection.getConnectTimeMillis(),
987                     connection.getStatusHints(),
988                     connection.getDisconnectCause(),
989                     emptyList,
990                     connection.getExtras());
991             mAdapter.addExistingConnection(id, parcelableConnection);
992         }
993     }
994 
995     /**
996      * Returns all the active {@code Connection}s for which this {@code ConnectionService}
997      * has taken responsibility.
998      *
999      * @return A collection of {@code Connection}s created by this {@code ConnectionService}.
1000      */
getAllConnections()1001     public final Collection<Connection> getAllConnections() {
1002         return mConnectionById.values();
1003     }
1004 
1005     /**
1006      * Create a {@code Connection} given an incoming request. This is used to attach to existing
1007      * incoming calls.
1008      *
1009      * @param connectionManagerPhoneAccount See description at
1010      *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
1011      * @param request Details about the incoming call.
1012      * @return The {@code Connection} object to satisfy this call, or {@code null} to
1013      *         not handle the call.
1014      */
onCreateIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1015     public Connection onCreateIncomingConnection(
1016             PhoneAccountHandle connectionManagerPhoneAccount,
1017             ConnectionRequest request) {
1018         return null;
1019     }
1020 
1021     /**
1022      * Trigger recalculate functinality for conference calls. This is used when a Telephony
1023      * Connection is part of a conference controller but is not yet added to Connection
1024      * Service and hence cannot be added to the conference call.
1025      *
1026      * @hide
1027      */
triggerConferenceRecalculate()1028     public void triggerConferenceRecalculate() {
1029     }
1030 
1031     /**
1032      * Create a {@code Connection} given an outgoing request. This is used to initiate new
1033      * outgoing calls.
1034      *
1035      * @param connectionManagerPhoneAccount The connection manager account to use for managing
1036      *         this call.
1037      *         <p>
1038      *         If this parameter is not {@code null}, it means that this {@code ConnectionService}
1039      *         has registered one or more {@code PhoneAccount}s having
1040      *         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}. This parameter will contain
1041      *         one of these {@code PhoneAccount}s, while the {@code request} will contain another
1042      *         (usually but not always distinct) {@code PhoneAccount} to be used for actually
1043      *         making the connection.
1044      *         <p>
1045      *         If this parameter is {@code null}, it means that this {@code ConnectionService} is
1046      *         being asked to make a direct connection. The
1047      *         {@link ConnectionRequest#getAccountHandle()} of parameter {@code request} will be
1048      *         a {@code PhoneAccount} registered by this {@code ConnectionService} to use for
1049      *         making the connection.
1050      * @param request Details about the outgoing call.
1051      * @return The {@code Connection} object to satisfy this call, or the result of an invocation
1052      *         of {@link Connection#createFailedConnection(DisconnectCause)} to not handle the call.
1053      */
onCreateOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1054     public Connection onCreateOutgoingConnection(
1055             PhoneAccountHandle connectionManagerPhoneAccount,
1056             ConnectionRequest request) {
1057         return null;
1058     }
1059 
1060     /**
1061      * Create a {@code Connection} for a new unknown call. An unknown call is a call originating
1062      * from the ConnectionService that was neither a user-initiated outgoing call, nor an incoming
1063      * call created using
1064      * {@code TelecomManager#addNewIncomingCall(PhoneAccountHandle, android.os.Bundle)}.
1065      *
1066      * @param connectionManagerPhoneAccount
1067      * @param request
1068      * @return
1069      *
1070      * @hide
1071      */
onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1072     public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount,
1073             ConnectionRequest request) {
1074        return null;
1075     }
1076 
1077     /**
1078      * Conference two specified connections. Invoked when the user has made a request to merge the
1079      * specified connections into a conference call. In response, the connection service should
1080      * create an instance of {@link Conference} and pass it into {@link #addConference}.
1081      *
1082      * @param connection1 A connection to merge into a conference call.
1083      * @param connection2 A connection to merge into a conference call.
1084      */
onConference(Connection connection1, Connection connection2)1085     public void onConference(Connection connection1, Connection connection2) {}
1086 
1087     /**
1088      * Indicates that a remote conference has been created for existing {@link RemoteConnection}s.
1089      * When this method is invoked, this {@link ConnectionService} should create its own
1090      * representation of the conference call and send it to telecom using {@link #addConference}.
1091      * <p>
1092      * This is only relevant to {@link ConnectionService}s which are registered with
1093      * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}.
1094      *
1095      * @param conference The remote conference call.
1096      */
onRemoteConferenceAdded(RemoteConference conference)1097     public void onRemoteConferenceAdded(RemoteConference conference) {}
1098 
1099     /**
1100      * Called when an existing connection is added remotely.
1101      * @param connection The existing connection which was added.
1102      */
onRemoteExistingConnectionAdded(RemoteConnection connection)1103     public void onRemoteExistingConnectionAdded(RemoteConnection connection) {}
1104 
1105     /**
1106      * @hide
1107      */
containsConference(Conference conference)1108     public boolean containsConference(Conference conference) {
1109         return mIdByConference.containsKey(conference);
1110     }
1111 
1112     /** {@hide} */
addRemoteConference(RemoteConference remoteConference)1113     void addRemoteConference(RemoteConference remoteConference) {
1114         onRemoteConferenceAdded(remoteConference);
1115     }
1116 
1117     /** {@hide} */
addRemoteExistingConnection(RemoteConnection remoteConnection)1118     void addRemoteExistingConnection(RemoteConnection remoteConnection) {
1119         onRemoteExistingConnectionAdded(remoteConnection);
1120     }
1121 
onAccountsInitialized()1122     private void onAccountsInitialized() {
1123         mAreAccountsInitialized = true;
1124         for (Runnable r : mPreInitializationConnectionRequests) {
1125             r.run();
1126         }
1127         mPreInitializationConnectionRequests.clear();
1128     }
1129 
1130     /**
1131      * Adds an existing connection to the list of connections, identified by a new UUID.
1132      *
1133      * @param connection The connection.
1134      * @return The UUID of the connection (e.g. the call-id).
1135      */
addExistingConnectionInternal(Connection connection)1136     private String addExistingConnectionInternal(Connection connection) {
1137         String id = UUID.randomUUID().toString();
1138         addConnection(id, connection);
1139         return id;
1140     }
1141 
addConnection(String callId, Connection connection)1142     private void addConnection(String callId, Connection connection) {
1143         mConnectionById.put(callId, connection);
1144         mIdByConnection.put(connection, callId);
1145         connection.addConnectionListener(mConnectionListener);
1146         connection.setConnectionService(this);
1147     }
1148 
1149     /** {@hide} */
removeConnection(Connection connection)1150     protected void removeConnection(Connection connection) {
1151         String id = mIdByConnection.get(connection);
1152         connection.unsetConnectionService(this);
1153         connection.removeConnectionListener(mConnectionListener);
1154         mConnectionById.remove(mIdByConnection.get(connection));
1155         mIdByConnection.remove(connection);
1156         mAdapter.removeCall(id);
1157     }
1158 
addConferenceInternal(Conference conference)1159     private String addConferenceInternal(Conference conference) {
1160         if (mIdByConference.containsKey(conference)) {
1161             Log.w(this, "Re-adding an existing conference: %s.", conference);
1162         } else if (conference != null) {
1163             String id = UUID.randomUUID().toString();
1164             mConferenceById.put(id, conference);
1165             mIdByConference.put(conference, id);
1166             conference.addListener(mConferenceListener);
1167             return id;
1168         }
1169 
1170         return null;
1171     }
1172 
removeConference(Conference conference)1173     private void removeConference(Conference conference) {
1174         if (mIdByConference.containsKey(conference)) {
1175             conference.removeListener(mConferenceListener);
1176 
1177             String id = mIdByConference.get(conference);
1178             mConferenceById.remove(id);
1179             mIdByConference.remove(conference);
1180             mAdapter.removeCall(id);
1181         }
1182     }
1183 
findConnectionForAction(String callId, String action)1184     private Connection findConnectionForAction(String callId, String action) {
1185         if (mConnectionById.containsKey(callId)) {
1186             return mConnectionById.get(callId);
1187         }
1188         Log.w(this, "%s - Cannot find Connection %s", action, callId);
1189         return getNullConnection();
1190     }
1191 
getNullConnection()1192     static synchronized Connection getNullConnection() {
1193         if (sNullConnection == null) {
1194             sNullConnection = new Connection() {};
1195         }
1196         return sNullConnection;
1197     }
1198 
findConferenceForAction(String conferenceId, String action)1199     private Conference findConferenceForAction(String conferenceId, String action) {
1200         if (mConferenceById.containsKey(conferenceId)) {
1201             return mConferenceById.get(conferenceId);
1202         }
1203         Log.w(this, "%s - Cannot find conference %s", action, conferenceId);
1204         return getNullConference();
1205     }
1206 
createConnectionIdList(List<Connection> connections)1207     private List<String> createConnectionIdList(List<Connection> connections) {
1208         List<String> ids = new ArrayList<>();
1209         for (Connection c : connections) {
1210             if (mIdByConnection.containsKey(c)) {
1211                 ids.add(mIdByConnection.get(c));
1212             }
1213         }
1214         Collections.sort(ids);
1215         return ids;
1216     }
1217 
1218     /**
1219      * Builds a list of {@link Connection} and {@link Conference} IDs based on the list of
1220      * {@link Conferenceable}s passed in.
1221      *
1222      * @param conferenceables The {@link Conferenceable} connections and conferences.
1223      * @return List of string conference and call Ids.
1224      */
createIdList(List<Conferenceable> conferenceables)1225     private List<String> createIdList(List<Conferenceable> conferenceables) {
1226         List<String> ids = new ArrayList<>();
1227         for (Conferenceable c : conferenceables) {
1228             // Only allow Connection and Conference conferenceables.
1229             if (c instanceof Connection) {
1230                 Connection connection = (Connection) c;
1231                 if (mIdByConnection.containsKey(connection)) {
1232                     ids.add(mIdByConnection.get(connection));
1233                 }
1234             } else if (c instanceof Conference) {
1235                 Conference conference = (Conference) c;
1236                 if (mIdByConference.containsKey(conference)) {
1237                     ids.add(mIdByConference.get(conference));
1238                 }
1239             }
1240         }
1241         Collections.sort(ids);
1242         return ids;
1243     }
1244 
getNullConference()1245     private Conference getNullConference() {
1246         if (sNullConference == null) {
1247             sNullConference = new Conference(null) {};
1248         }
1249         return sNullConference;
1250     }
1251 
endAllConnections()1252     private void endAllConnections() {
1253         // Unbound from telecomm.  We should end all connections and conferences.
1254         for (Connection connection : mIdByConnection.keySet()) {
1255             // only operate on top-level calls. Conference calls will be removed on their own.
1256             if (connection.getConference() == null) {
1257                 connection.onDisconnect();
1258             }
1259         }
1260         for (Conference conference : mIdByConference.keySet()) {
1261             conference.onDisconnect();
1262         }
1263     }
1264 }
1265