• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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 com.android.server.telecom.tests;
18 
19 import com.android.internal.telecom.IConnectionService;
20 import com.android.internal.telecom.IConnectionServiceAdapter;
21 import com.android.internal.telecom.IVideoProvider;
22 import com.android.internal.telecom.RemoteServiceCallback;
23 import com.android.server.telecom.Log;
24 
25 import junit.framework.TestCase;
26 
27 import org.mockito.Mockito;
28 
29 import android.content.ComponentName;
30 import android.net.Uri;
31 import android.os.Bundle;
32 import android.os.IBinder;
33 import android.os.IInterface;
34 import android.os.RemoteException;
35 import android.telecom.CallAudioState;
36 import android.telecom.Conference;
37 import android.telecom.Connection;
38 import android.telecom.ConnectionRequest;
39 import android.telecom.ConnectionService;
40 import android.telecom.DisconnectCause;
41 import android.telecom.ParcelableConference;
42 import android.telecom.ParcelableConnection;
43 import android.telecom.PhoneAccountHandle;
44 import android.telecom.StatusHints;
45 import android.telecom.TelecomManager;
46 
47 import com.google.android.collect.Lists;
48 
49 import java.lang.Override;
50 import java.util.ArrayList;
51 import java.util.Collection;
52 import java.util.HashMap;
53 import java.util.HashSet;
54 import java.util.List;
55 import java.util.Map;
56 import java.util.Set;
57 import java.util.concurrent.CountDownLatch;
58 import java.util.concurrent.TimeUnit;
59 
60 /**
61  * Controls a test {@link IConnectionService} as would be provided by a source of connectivity
62  * to the Telecom framework.
63  */
64 public class ConnectionServiceFixture implements TestFixture<IConnectionService> {
65     static int INVALID_VIDEO_STATE = -1;
66     public CountDownLatch mExtrasLock = new CountDownLatch(1);
67     static int NOT_SPECIFIED = 0;
68 
69     /**
70      * Implementation of ConnectionService that performs no-ops for tasks normally meant for
71      * Telephony and reports success back to Telecom
72      */
73     public class FakeConnectionServiceDelegate extends ConnectionService {
74         int mVideoState = INVALID_VIDEO_STATE;
75         int mCapabilities = NOT_SPECIFIED;
76         int mProperties = NOT_SPECIFIED;
77 
78         @Override
onCreateUnknownConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)79         public Connection onCreateUnknownConnection(
80                 PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
81             mLatestConnection = new FakeConnection(request.getVideoState(), request.getAddress());
82             return mLatestConnection;
83         }
84 
85         @Override
onCreateIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)86         public Connection onCreateIncomingConnection(
87                 PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
88             FakeConnection fakeConnection =  new FakeConnection(
89                     mVideoState == INVALID_VIDEO_STATE ? request.getVideoState() : mVideoState,
90                     request.getAddress());
91             mLatestConnection = fakeConnection;
92             if (mCapabilities != NOT_SPECIFIED) {
93                 fakeConnection.setConnectionCapabilities(mCapabilities);
94             }
95             if (mProperties != NOT_SPECIFIED) {
96                 fakeConnection.setConnectionProperties(mProperties);
97             }
98 
99             return fakeConnection;
100         }
101 
102         @Override
onCreateOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)103         public Connection onCreateOutgoingConnection(
104                 PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
105             FakeConnection fakeConnection = new FakeConnection(request.getVideoState(),
106                     request.getAddress());
107             mLatestConnection = fakeConnection;
108             if (mCapabilities != NOT_SPECIFIED) {
109                 fakeConnection.setConnectionCapabilities(mCapabilities);
110             }
111             if (mProperties != NOT_SPECIFIED) {
112                 fakeConnection.setConnectionProperties(mProperties);
113             }
114             return fakeConnection;
115         }
116 
117         @Override
onConference(Connection cxn1, Connection cxn2)118         public void onConference(Connection cxn1, Connection cxn2) {
119             if (((FakeConnection) cxn1).getIsConferenceCreated()) {
120                 // Usually, this is implemented by something in Telephony, which does a bunch of
121                 // radio work to conference the two connections together. Here we just short-cut
122                 // that and declare them conferenced.
123                 Conference fakeConference = new FakeConference();
124                 fakeConference.addConnection(cxn1);
125                 fakeConference.addConnection(cxn2);
126                 mLatestConference = fakeConference;
127                 addConference(fakeConference);
128             } else {
129                 try {
130                     sendSetConferenceMergeFailed(cxn1.getTelecomCallId());
131                 } catch (Exception e) {
132                     Log.w(this, "Exception on sendSetConferenceMergeFailed: " + e.getMessage());
133                 }
134             }
135         }
136     }
137 
138     public class FakeConnection extends Connection {
139         // Set to false if you wish the Conference merge to fail.
140         boolean mIsConferenceCreated = true;
141 
FakeConnection(int videoState, Uri address)142         public FakeConnection(int videoState, Uri address) {
143             super();
144             int capabilities = getConnectionCapabilities();
145             capabilities |= CAPABILITY_MUTE;
146             capabilities |= CAPABILITY_SUPPORT_HOLD;
147             capabilities |= CAPABILITY_HOLD;
148             setVideoState(videoState);
149             setConnectionCapabilities(capabilities);
150             setDialing();
151             setAddress(address, TelecomManager.PRESENTATION_ALLOWED);
152         }
153 
154         @Override
onExtrasChanged(Bundle extras)155         public void onExtrasChanged(Bundle extras) {
156             mExtrasLock.countDown();
157         }
158 
getIsConferenceCreated()159         public boolean getIsConferenceCreated() {
160             return mIsConferenceCreated;
161         }
162 
setIsConferenceCreated(boolean isConferenceCreated)163         public void setIsConferenceCreated(boolean isConferenceCreated) {
164             mIsConferenceCreated = isConferenceCreated;
165         }
166     }
167 
168     public class FakeConference extends Conference {
FakeConference()169         public FakeConference() {
170             super(null);
171             setConnectionCapabilities(
172                     Connection.CAPABILITY_SUPPORT_HOLD
173                             | Connection.CAPABILITY_HOLD
174                             | Connection.CAPABILITY_MUTE
175                             | Connection.CAPABILITY_MANAGE_CONFERENCE);
176         }
177 
178         @Override
onMerge(Connection connection)179         public void onMerge(Connection connection) {
180             // Do nothing besides inform the connection that it was merged into this conference.
181             connection.setConference(this);
182         }
183 
184         @Override
onExtrasChanged(Bundle extras)185         public void onExtrasChanged(Bundle extras) {
186             Log.w(this, "FakeConference onExtrasChanged");
187             mExtrasLock.countDown();
188         }
189     }
190 
191     public class FakeConnectionService extends IConnectionService.Stub {
192         List<String> rejectedCallIds = Lists.newArrayList();
193 
194         @Override
addConnectionServiceAdapter(IConnectionServiceAdapter adapter)195         public void addConnectionServiceAdapter(IConnectionServiceAdapter adapter)
196                 throws RemoteException {
197             if (!mConnectionServiceAdapters.add(adapter)) {
198                 throw new RuntimeException("Adapter already added: " + adapter);
199             }
200             mConnectionServiceDelegateAdapter.addConnectionServiceAdapter(adapter);
201         }
202 
203         @Override
removeConnectionServiceAdapter(IConnectionServiceAdapter adapter)204         public void removeConnectionServiceAdapter(IConnectionServiceAdapter adapter)
205                 throws RemoteException {
206             if (!mConnectionServiceAdapters.remove(adapter)) {
207                 throw new RuntimeException("Adapter never added: " + adapter);
208             }
209             mConnectionServiceDelegateAdapter.removeConnectionServiceAdapter(adapter);
210         }
211 
212         @Override
createConnection(PhoneAccountHandle connectionManagerPhoneAccount, String id, ConnectionRequest request, boolean isIncoming, boolean isUnknown)213         public void createConnection(PhoneAccountHandle connectionManagerPhoneAccount,
214                 String id,
215                 ConnectionRequest request, boolean isIncoming, boolean isUnknown)
216                 throws RemoteException {
217             Log.i(ConnectionServiceFixture.this, "xoxox createConnection --> " + id);
218 
219             if (mConnectionById.containsKey(id)) {
220                 throw new RuntimeException("Connection already exists: " + id);
221             }
222             mLatestConnectionId = id;
223             ConnectionInfo c = new ConnectionInfo();
224             c.connectionManagerPhoneAccount = connectionManagerPhoneAccount;
225             c.id = id;
226             c.request = request;
227             c.isIncoming = isIncoming;
228             c.isUnknown = isUnknown;
229             c.capabilities |= Connection.CAPABILITY_HOLD | Connection.CAPABILITY_SUPPORT_HOLD;
230             c.videoState = request.getVideoState();
231             c.mockVideoProvider = new MockVideoProvider();
232             c.videoProvider = c.mockVideoProvider.getInterface();
233             c.isConferenceCreated = true;
234             mConnectionById.put(id, c);
235             mConnectionServiceDelegateAdapter.createConnection(connectionManagerPhoneAccount,
236                     id, request, isIncoming, isUnknown);
237         }
238 
239         @Override
abort(String callId)240         public void abort(String callId) throws RemoteException { }
241 
242         @Override
answerVideo(String callId, int videoState)243         public void answerVideo(String callId, int videoState) throws RemoteException { }
244 
245         @Override
answer(String callId)246         public void answer(String callId) throws RemoteException { }
247 
248         @Override
reject(String callId)249         public void reject(String callId) throws RemoteException {
250             rejectedCallIds.add(callId);
251         }
252 
253         @Override
rejectWithMessage(String callId, String message)254         public void rejectWithMessage(String callId, String message) throws RemoteException { }
255 
256         @Override
disconnect(String callId)257         public void disconnect(String callId) throws RemoteException { }
258 
259         @Override
silence(String callId)260         public void silence(String callId) throws RemoteException { }
261 
262         @Override
hold(String callId)263         public void hold(String callId) throws RemoteException { }
264 
265         @Override
unhold(String callId)266         public void unhold(String callId) throws RemoteException { }
267 
268         @Override
onCallAudioStateChanged(String activeCallId, CallAudioState audioState)269         public void onCallAudioStateChanged(String activeCallId, CallAudioState audioState)
270                 throws RemoteException { }
271 
272         @Override
playDtmfTone(String callId, char digit)273         public void playDtmfTone(String callId, char digit) throws RemoteException { }
274 
275         @Override
stopDtmfTone(String callId)276         public void stopDtmfTone(String callId) throws RemoteException { }
277 
278         @Override
conference(String conferenceCallId, String callId)279         public void conference(String conferenceCallId, String callId) throws RemoteException {
280             mConnectionServiceDelegateAdapter.conference(conferenceCallId, callId);
281         }
282 
283         @Override
splitFromConference(String callId)284         public void splitFromConference(String callId) throws RemoteException { }
285 
286         @Override
mergeConference(String conferenceCallId)287         public void mergeConference(String conferenceCallId) throws RemoteException { }
288 
289         @Override
swapConference(String conferenceCallId)290         public void swapConference(String conferenceCallId) throws RemoteException { }
291 
292         @Override
onPostDialContinue(String callId, boolean proceed)293         public void onPostDialContinue(String callId, boolean proceed) throws RemoteException { }
294 
295         @Override
pullExternalCall(String callId)296         public void pullExternalCall(String callId) throws RemoteException { }
297 
298         @Override
sendCallEvent(String callId, String event, Bundle extras)299         public void sendCallEvent(String callId, String event, Bundle extras) throws RemoteException
300         {}
301 
onExtrasChanged(String callId, Bundle extras)302         public void onExtrasChanged(String callId, Bundle extras) throws RemoteException {
303             mConnectionServiceDelegateAdapter.onExtrasChanged(callId, extras);
304         }
305 
306         @Override
asBinder()307         public IBinder asBinder() {
308             return this;
309         }
310 
311         @Override
queryLocalInterface(String descriptor)312         public IInterface queryLocalInterface(String descriptor) {
313             return this;
314         }
315     }
316 
317     FakeConnectionServiceDelegate mConnectionServiceDelegate =
318             new FakeConnectionServiceDelegate();
319     private IConnectionService mConnectionServiceDelegateAdapter =
320             IConnectionService.Stub.asInterface(mConnectionServiceDelegate.onBind(null));
321 
322     FakeConnectionService mConnectionService = new FakeConnectionService();
323     private IConnectionService.Stub mConnectionServiceSpy = Mockito.spy(mConnectionService);
324 
325     public class ConnectionInfo {
326         PhoneAccountHandle connectionManagerPhoneAccount;
327         String id;
328         boolean ringing;
329         ConnectionRequest request;
330         boolean isIncoming;
331         boolean isUnknown;
332         int state;
333         int addressPresentation;
334         int capabilities;
335         int properties;
336         StatusHints statusHints;
337         DisconnectCause disconnectCause;
338         String conferenceId;
339         String callerDisplayName;
340         int callerDisplayNamePresentation;
341         final List<String> conferenceableConnectionIds = new ArrayList<>();
342         IVideoProvider videoProvider;
343         Connection.VideoProvider videoProviderImpl;
344         MockVideoProvider mockVideoProvider;
345         int videoState;
346         boolean isVoipAudioMode;
347         Bundle extras;
348         boolean isConferenceCreated;
349     }
350 
351     public class ConferenceInfo {
352         PhoneAccountHandle phoneAccount;
353         int state;
354         int capabilities;
355         int properties;
356         final List<String> connectionIds = new ArrayList<>();
357         IVideoProvider videoProvider;
358         int videoState;
359         long connectTimeMillis;
360         StatusHints statusHints;
361         Bundle extras;
362     }
363 
364     public String mLatestConnectionId;
365     public Connection mLatestConnection;
366     public Conference mLatestConference;
367     public final Set<IConnectionServiceAdapter> mConnectionServiceAdapters = new HashSet<>();
368     public final Map<String, ConnectionInfo> mConnectionById = new HashMap<>();
369     public final Map<String, ConferenceInfo> mConferenceById = new HashMap<>();
370     public final List<ComponentName> mRemoteConnectionServiceNames = new ArrayList<>();
371     public final List<IBinder> mRemoteConnectionServices = new ArrayList<>();
372 
ConnectionServiceFixture()373     public ConnectionServiceFixture() throws Exception { }
374 
375     @Override
getTestDouble()376     public IConnectionService getTestDouble() {
377         return mConnectionServiceSpy;
378     }
379 
sendHandleCreateConnectionComplete(String id)380     public void sendHandleCreateConnectionComplete(String id) throws Exception {
381         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
382             a.handleCreateConnectionComplete(
383                     id,
384                     mConnectionById.get(id).request,
385                     parcelable(mConnectionById.get(id)));
386         }
387     }
388 
sendSetActive(String id)389     public void sendSetActive(String id) throws Exception {
390         mConnectionById.get(id).state = Connection.STATE_ACTIVE;
391         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
392             a.setActive(id);
393         }
394     }
395 
sendSetRinging(String id)396     public void sendSetRinging(String id) throws Exception {
397         mConnectionById.get(id).state = Connection.STATE_RINGING;
398         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
399             a.setRinging(id);
400         }
401     }
402 
sendSetDialing(String id)403     public void sendSetDialing(String id) throws Exception {
404         mConnectionById.get(id).state = Connection.STATE_DIALING;
405         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
406             a.setDialing(id);
407         }
408     }
409 
sendSetDisconnected(String id, int disconnectCause)410     public void sendSetDisconnected(String id, int disconnectCause) throws Exception {
411         mConnectionById.get(id).state = Connection.STATE_DISCONNECTED;
412         mConnectionById.get(id).disconnectCause = new DisconnectCause(disconnectCause);
413         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
414             a.setDisconnected(id, mConnectionById.get(id).disconnectCause);
415         }
416     }
417 
sendSetOnHold(String id)418     public void sendSetOnHold(String id) throws Exception {
419         mConnectionById.get(id).state = Connection.STATE_HOLDING;
420         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
421             a.setOnHold(id);
422         }
423     }
424 
sendSetRingbackRequested(String id)425     public void sendSetRingbackRequested(String id) throws Exception {
426         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
427             a.setRingbackRequested(id, mConnectionById.get(id).ringing);
428         }
429     }
430 
sendSetConnectionCapabilities(String id)431     public void sendSetConnectionCapabilities(String id) throws Exception {
432         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
433             a.setConnectionCapabilities(id, mConnectionById.get(id).capabilities);
434         }
435     }
436 
sendSetIsConferenced(String id)437     public void sendSetIsConferenced(String id) throws Exception {
438         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
439             a.setIsConferenced(id, mConnectionById.get(id).conferenceId);
440         }
441     }
442 
sendAddConferenceCall(String id)443     public void sendAddConferenceCall(String id) throws Exception {
444         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
445             a.addConferenceCall(id, parcelable(mConferenceById.get(id)));
446         }
447     }
448 
sendRemoveCall(String id)449     public void sendRemoveCall(String id) throws Exception {
450         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
451             a.removeCall(id);
452         }
453     }
454 
sendOnPostDialWait(String id, String remaining)455     public void sendOnPostDialWait(String id, String remaining) throws Exception {
456         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
457             a.onPostDialWait(id, remaining);
458         }
459     }
460 
sendOnPostDialChar(String id, char nextChar)461     public void sendOnPostDialChar(String id, char nextChar) throws Exception {
462         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
463             a.onPostDialChar(id, nextChar);
464         }
465     }
466 
sendQueryRemoteConnectionServices()467     public void sendQueryRemoteConnectionServices() throws Exception {
468         mRemoteConnectionServices.clear();
469         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
470             a.queryRemoteConnectionServices(new RemoteServiceCallback.Stub() {
471                 @Override
472                 public void onError() throws RemoteException {
473                     throw new RuntimeException();
474                 }
475 
476                 @Override
477                 public void onResult(
478                         List<ComponentName> names,
479                         List<IBinder> services)
480                         throws RemoteException {
481                     TestCase.assertEquals(names.size(), services.size());
482                     mRemoteConnectionServiceNames.addAll(names);
483                     mRemoteConnectionServices.addAll(services);
484                 }
485 
486                 @Override
487                 public IBinder asBinder() {
488                     return this;
489                 }
490             });
491         }
492     }
493 
sendSetVideoProvider(String id)494     public void sendSetVideoProvider(String id) throws Exception {
495         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
496             a.setVideoProvider(id, mConnectionById.get(id).videoProvider);
497         }
498     }
499 
sendSetVideoState(String id)500     public void sendSetVideoState(String id) throws Exception {
501         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
502             a.setVideoState(id, mConnectionById.get(id).videoState);
503         }
504     }
505 
sendSetIsVoipAudioMode(String id)506     public void sendSetIsVoipAudioMode(String id) throws Exception {
507         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
508             a.setIsVoipAudioMode(id, mConnectionById.get(id).isVoipAudioMode);
509         }
510     }
511 
sendSetStatusHints(String id)512     public void sendSetStatusHints(String id) throws Exception {
513         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
514             a.setStatusHints(id, mConnectionById.get(id).statusHints);
515         }
516     }
517 
sendSetAddress(String id)518     public void sendSetAddress(String id) throws Exception {
519         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
520             a.setAddress(
521                     id,
522                     mConnectionById.get(id).request.getAddress(),
523                     mConnectionById.get(id).addressPresentation);
524         }
525     }
526 
sendSetCallerDisplayName(String id)527     public void sendSetCallerDisplayName(String id) throws Exception {
528         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
529             a.setCallerDisplayName(
530                     id,
531                     mConnectionById.get(id).callerDisplayName,
532                     mConnectionById.get(id).callerDisplayNamePresentation);
533         }
534     }
535 
sendSetConferenceableConnections(String id)536     public void sendSetConferenceableConnections(String id) throws Exception {
537         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
538             a.setConferenceableConnections(id, mConnectionById.get(id).conferenceableConnectionIds);
539         }
540     }
541 
sendAddExistingConnection(String id)542     public void sendAddExistingConnection(String id) throws Exception {
543         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
544             a.addExistingConnection(id, parcelable(mConnectionById.get(id)));
545         }
546     }
547 
sendConnectionEvent(String id, String event, Bundle extras)548     public void sendConnectionEvent(String id, String event, Bundle extras) throws Exception {
549         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
550             a.onConnectionEvent(id, event, extras);
551         }
552     }
553 
sendSetConferenceMergeFailed(String id)554     public void sendSetConferenceMergeFailed(String id) throws Exception {
555         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
556             a.setConferenceMergeFailed(id);
557         }
558     }
559 
560     /**
561      * Waits until the {@link Connection#onExtrasChanged(Bundle)} API has been called on a
562      * {@link Connection} or {@link Conference}.
563      */
waitForExtras()564     public void waitForExtras() {
565         try {
566             mExtrasLock.await(TelecomSystemTest.TEST_TIMEOUT, TimeUnit.MILLISECONDS);
567         } catch (InterruptedException ie) {
568         }
569         mExtrasLock = new CountDownLatch(1);
570     }
571 
parcelable(ConferenceInfo c)572     private ParcelableConference parcelable(ConferenceInfo c) {
573         return new ParcelableConference(
574                 c.phoneAccount,
575                 c.state,
576                 c.capabilities,
577                 c.properties,
578                 c.connectionIds,
579                 c.videoProvider,
580                 c.videoState,
581                 c.connectTimeMillis,
582                 c.statusHints,
583                 c.extras);
584     }
585 
parcelable(ConnectionInfo c)586     private ParcelableConnection parcelable(ConnectionInfo c) {
587         return new ParcelableConnection(
588                 c.request.getAccountHandle(),
589                 c.state,
590                 c.capabilities,
591                 c.properties,
592                 c.request.getAddress(),
593                 c.addressPresentation,
594                 c.callerDisplayName,
595                 c.callerDisplayNamePresentation,
596                 c.videoProvider,
597                 c.videoState,
598                 false, /* ringback requested */
599                 false, /* voip audio mode */
600                 0, /* Connect Time for conf call on this connection */
601                 c.statusHints,
602                 c.disconnectCause,
603                 c.conferenceableConnectionIds,
604                 c.extras);
605     }
606 }
607