ConnectionService.java revision 1c4eb10cc1159d5e029f982a629c5262419fa5ce
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
17package android.telecom;
18
19import android.annotation.SdkConstant;
20import android.app.Service;
21import android.content.ComponentName;
22import android.content.Intent;
23import android.net.Uri;
24import android.os.Handler;
25import android.os.IBinder;
26import android.os.Looper;
27import android.os.Message;
28
29import com.android.internal.os.SomeArgs;
30import com.android.internal.telecom.IConnectionService;
31import com.android.internal.telecom.IConnectionServiceAdapter;
32import com.android.internal.telecom.RemoteServiceCallback;
33
34import java.util.ArrayList;
35import java.util.Collection;
36import java.util.Collections;
37import java.util.HashMap;
38import java.util.List;
39import java.util.Map;
40import java.util.UUID;
41
42/**
43 * A {@link android.app.Service} that provides telephone connections to processes running on an
44 * Android device.
45 */
46public abstract class ConnectionService extends Service {
47    /**
48     * The {@link Intent} that must be declared as handled by the service.
49     */
50    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
51    public static final String SERVICE_INTERFACE = "android.telecom.ConnectionService";
52
53    // Flag controlling whether PII is emitted into the logs
54    private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG);
55
56    private static final int MSG_ADD_CONNECTION_SERVICE_ADAPTER = 1;
57    private static final int MSG_CREATE_CONNECTION = 2;
58    private static final int MSG_ABORT = 3;
59    private static final int MSG_ANSWER = 4;
60    private static final int MSG_REJECT = 5;
61    private static final int MSG_DISCONNECT = 6;
62    private static final int MSG_HOLD = 7;
63    private static final int MSG_UNHOLD = 8;
64    private static final int MSG_ON_AUDIO_STATE_CHANGED = 9;
65    private static final int MSG_PLAY_DTMF_TONE = 10;
66    private static final int MSG_STOP_DTMF_TONE = 11;
67    private static final int MSG_CONFERENCE = 12;
68    private static final int MSG_SPLIT_FROM_CONFERENCE = 13;
69    private static final int MSG_ON_POST_DIAL_CONTINUE = 14;
70    private static final int MSG_REMOVE_CONNECTION_SERVICE_ADAPTER = 16;
71    private static final int MSG_ANSWER_VIDEO = 17;
72    private static final int MSG_MERGE_CONFERENCE = 18;
73    private static final int MSG_SWAP_CONFERENCE = 19;
74
75    private static Connection sNullConnection;
76
77    private final Map<String, Connection> mConnectionById = new HashMap<>();
78    private final Map<Connection, String> mIdByConnection = new HashMap<>();
79    private final Map<String, Conference> mConferenceById = new HashMap<>();
80    private final Map<Conference, String> mIdByConference = new HashMap<>();
81    private final RemoteConnectionManager mRemoteConnectionManager =
82            new RemoteConnectionManager(this);
83    private final List<Runnable> mPreInitializationConnectionRequests = new ArrayList<>();
84    private final ConnectionServiceAdapter mAdapter = new ConnectionServiceAdapter();
85
86    private boolean mAreAccountsInitialized = false;
87    private Conference sNullConference;
88
89    private final IBinder mBinder = new IConnectionService.Stub() {
90        @Override
91        public void addConnectionServiceAdapter(IConnectionServiceAdapter adapter) {
92            mHandler.obtainMessage(MSG_ADD_CONNECTION_SERVICE_ADAPTER, adapter).sendToTarget();
93        }
94
95        public void removeConnectionServiceAdapter(IConnectionServiceAdapter adapter) {
96            mHandler.obtainMessage(MSG_REMOVE_CONNECTION_SERVICE_ADAPTER, adapter).sendToTarget();
97        }
98
99        @Override
100        public void createConnection(
101                PhoneAccountHandle connectionManagerPhoneAccount,
102                String id,
103                ConnectionRequest request,
104                boolean isIncoming) {
105            SomeArgs args = SomeArgs.obtain();
106            args.arg1 = connectionManagerPhoneAccount;
107            args.arg2 = id;
108            args.arg3 = request;
109            args.argi1 = isIncoming ? 1 : 0;
110            mHandler.obtainMessage(MSG_CREATE_CONNECTION, args).sendToTarget();
111        }
112
113        @Override
114        public void abort(String callId) {
115            mHandler.obtainMessage(MSG_ABORT, callId).sendToTarget();
116        }
117
118        @Override
119        /** @hide */
120        public void answerVideo(String callId, int videoState) {
121            SomeArgs args = SomeArgs.obtain();
122            args.arg1 = callId;
123            args.argi1 = videoState;
124            mHandler.obtainMessage(MSG_ANSWER_VIDEO, args).sendToTarget();
125        }
126
127        @Override
128        public void answer(String callId) {
129            mHandler.obtainMessage(MSG_ANSWER, callId).sendToTarget();
130        }
131
132        @Override
133        public void reject(String callId) {
134            mHandler.obtainMessage(MSG_REJECT, callId).sendToTarget();
135        }
136
137        @Override
138        public void disconnect(String callId) {
139            mHandler.obtainMessage(MSG_DISCONNECT, callId).sendToTarget();
140        }
141
142        @Override
143        public void hold(String callId) {
144            mHandler.obtainMessage(MSG_HOLD, callId).sendToTarget();
145        }
146
147        @Override
148        public void unhold(String callId) {
149            mHandler.obtainMessage(MSG_UNHOLD, callId).sendToTarget();
150        }
151
152        @Override
153        public void onAudioStateChanged(String callId, AudioState audioState) {
154            SomeArgs args = SomeArgs.obtain();
155            args.arg1 = callId;
156            args.arg2 = audioState;
157            mHandler.obtainMessage(MSG_ON_AUDIO_STATE_CHANGED, args).sendToTarget();
158        }
159
160        @Override
161        public void playDtmfTone(String callId, char digit) {
162            mHandler.obtainMessage(MSG_PLAY_DTMF_TONE, digit, 0, callId).sendToTarget();
163        }
164
165        @Override
166        public void stopDtmfTone(String callId) {
167            mHandler.obtainMessage(MSG_STOP_DTMF_TONE, callId).sendToTarget();
168        }
169
170        @Override
171        public void conference(String callId1, String callId2) {
172            SomeArgs args = SomeArgs.obtain();
173            args.arg1 = callId1;
174            args.arg2 = callId2;
175            mHandler.obtainMessage(MSG_CONFERENCE, args).sendToTarget();
176        }
177
178        @Override
179        public void splitFromConference(String callId) {
180            mHandler.obtainMessage(MSG_SPLIT_FROM_CONFERENCE, callId).sendToTarget();
181        }
182
183        @Override
184        public void mergeConference(String callId) {
185            mHandler.obtainMessage(MSG_MERGE_CONFERENCE, callId).sendToTarget();
186        }
187
188        @Override
189        public void swapConference(String callId) {
190            mHandler.obtainMessage(MSG_SWAP_CONFERENCE, callId).sendToTarget();
191        }
192
193        @Override
194        public void onPostDialContinue(String callId, boolean proceed) {
195            SomeArgs args = SomeArgs.obtain();
196            args.arg1 = callId;
197            args.argi1 = proceed ? 1 : 0;
198            mHandler.obtainMessage(MSG_ON_POST_DIAL_CONTINUE, args).sendToTarget();
199        }
200    };
201
202    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
203        @Override
204        public void handleMessage(Message msg) {
205            switch (msg.what) {
206                case MSG_ADD_CONNECTION_SERVICE_ADAPTER:
207                    mAdapter.addAdapter((IConnectionServiceAdapter) msg.obj);
208                    onAdapterAttached();
209                    break;
210                case MSG_REMOVE_CONNECTION_SERVICE_ADAPTER:
211                    mAdapter.removeAdapter((IConnectionServiceAdapter) msg.obj);
212                    break;
213                case MSG_CREATE_CONNECTION: {
214                    SomeArgs args = (SomeArgs) msg.obj;
215                    try {
216                        final PhoneAccountHandle connectionManagerPhoneAccount =
217                                (PhoneAccountHandle) args.arg1;
218                        final String id = (String) args.arg2;
219                        final ConnectionRequest request = (ConnectionRequest) args.arg3;
220                        final boolean isIncoming = args.argi1 == 1;
221                        if (!mAreAccountsInitialized) {
222                            Log.d(this, "Enqueueing pre-init request %s", id);
223                            mPreInitializationConnectionRequests.add(new Runnable() {
224                                @Override
225                                public void run() {
226                                    createConnection(
227                                            connectionManagerPhoneAccount,
228                                            id,
229                                            request,
230                                            isIncoming);
231                                }
232                            });
233                        } else {
234                            createConnection(
235                                    connectionManagerPhoneAccount,
236                                    id,
237                                    request,
238                                    isIncoming);
239                        }
240                    } finally {
241                        args.recycle();
242                    }
243                    break;
244                }
245                case MSG_ABORT:
246                    abort((String) msg.obj);
247                    break;
248                case MSG_ANSWER:
249                    answer((String) msg.obj);
250                    break;
251                case MSG_ANSWER_VIDEO: {
252                    SomeArgs args = (SomeArgs) msg.obj;
253                    try {
254                        String callId = (String) args.arg1;
255                        int videoState = args.argi1;
256                        answerVideo(callId, videoState);
257                    } finally {
258                        args.recycle();
259                    }
260                    break;
261                }
262                case MSG_REJECT:
263                    reject((String) msg.obj);
264                    break;
265                case MSG_DISCONNECT:
266                    disconnect((String) msg.obj);
267                    break;
268                case MSG_HOLD:
269                    hold((String) msg.obj);
270                    break;
271                case MSG_UNHOLD:
272                    unhold((String) msg.obj);
273                    break;
274                case MSG_ON_AUDIO_STATE_CHANGED: {
275                    SomeArgs args = (SomeArgs) msg.obj;
276                    try {
277                        String callId = (String) args.arg1;
278                        AudioState audioState = (AudioState) args.arg2;
279                        onAudioStateChanged(callId, audioState);
280                    } finally {
281                        args.recycle();
282                    }
283                    break;
284                }
285                case MSG_PLAY_DTMF_TONE:
286                    playDtmfTone((String) msg.obj, (char) msg.arg1);
287                    break;
288                case MSG_STOP_DTMF_TONE:
289                    stopDtmfTone((String) msg.obj);
290                    break;
291                case MSG_CONFERENCE: {
292                    SomeArgs args = (SomeArgs) msg.obj;
293                    try {
294                        String callId1 = (String) args.arg1;
295                        String callId2 = (String) args.arg2;
296                        conference(callId1, callId2);
297                    } finally {
298                        args.recycle();
299                    }
300                    break;
301                }
302                case MSG_SPLIT_FROM_CONFERENCE:
303                    splitFromConference((String) msg.obj);
304                    break;
305                case MSG_MERGE_CONFERENCE:
306                    mergeConference((String) msg.obj);
307                    break;
308                case MSG_SWAP_CONFERENCE:
309                    swapConference((String) msg.obj);
310                    break;
311                case MSG_ON_POST_DIAL_CONTINUE: {
312                    SomeArgs args = (SomeArgs) msg.obj;
313                    try {
314                        String callId = (String) args.arg1;
315                        boolean proceed = (args.argi1 == 1);
316                        onPostDialContinue(callId, proceed);
317                    } finally {
318                        args.recycle();
319                    }
320                    break;
321                }
322                default:
323                    break;
324            }
325        }
326    };
327
328    private final Conference.Listener mConferenceListener = new Conference.Listener() {
329        @Override
330        public void onStateChanged(Conference conference, int oldState, int newState) {
331            String id = mIdByConference.get(conference);
332            switch (newState) {
333                case Connection.STATE_ACTIVE:
334                    mAdapter.setActive(id);
335                    break;
336                case Connection.STATE_HOLDING:
337                    mAdapter.setOnHold(id);
338                    break;
339                case Connection.STATE_DISCONNECTED:
340                    // handled by onDisconnected
341                    break;
342            }
343        }
344
345        @Override
346        public void onDisconnected(Conference conference, DisconnectCause disconnectCause) {
347            String id = mIdByConference.get(conference);
348            mAdapter.setDisconnected(id, disconnectCause);
349        }
350
351        @Override
352        public void onConnectionAdded(Conference conference, Connection connection) {
353        }
354
355        @Override
356        public void onConnectionRemoved(Conference conference, Connection connection) {
357        }
358
359        @Override
360        public void onDestroyed(Conference conference) {
361            removeConference(conference);
362        }
363
364        @Override
365        public void onCapabilitiesChanged(Conference conference, int capabilities) {
366            String id = mIdByConference.get(conference);
367            Log.d(this, "call capabilities: conference: %s",
368                    PhoneCapabilities.toString(capabilities));
369            mAdapter.setCallCapabilities(id, capabilities);
370        }
371    };
372
373    private final Connection.Listener mConnectionListener = new Connection.Listener() {
374        @Override
375        public void onStateChanged(Connection c, int state) {
376            String id = mIdByConnection.get(c);
377            Log.d(this, "Adapter set state %s %s", id, Connection.stateToString(state));
378            switch (state) {
379                case Connection.STATE_ACTIVE:
380                    mAdapter.setActive(id);
381                    break;
382                case Connection.STATE_DIALING:
383                    mAdapter.setDialing(id);
384                    break;
385                case Connection.STATE_DISCONNECTED:
386                    // Handled in onDisconnected()
387                    break;
388                case Connection.STATE_HOLDING:
389                    mAdapter.setOnHold(id);
390                    break;
391                case Connection.STATE_NEW:
392                    // Nothing to tell Telecom
393                    break;
394                case Connection.STATE_RINGING:
395                    mAdapter.setRinging(id);
396                    break;
397            }
398        }
399
400        @Override
401        public void onDisconnected(Connection c, DisconnectCause disconnectCause) {
402            String id = mIdByConnection.get(c);
403            Log.d(this, "Adapter set disconnected %s", disconnectCause);
404            mAdapter.setDisconnected(id, disconnectCause);
405        }
406
407        @Override
408        public void onVideoStateChanged(Connection c, int videoState) {
409            String id = mIdByConnection.get(c);
410            Log.d(this, "Adapter set video state %d", videoState);
411            mAdapter.setVideoState(id, videoState);
412        }
413
414        @Override
415        public void onAddressChanged(Connection c, Uri address, int presentation) {
416            String id = mIdByConnection.get(c);
417            mAdapter.setAddress(id, address, presentation);
418        }
419
420        @Override
421        public void onCallerDisplayNameChanged(
422                Connection c, String callerDisplayName, int presentation) {
423            String id = mIdByConnection.get(c);
424            mAdapter.setCallerDisplayName(id, callerDisplayName, presentation);
425        }
426
427        @Override
428        public void onDestroyed(Connection c) {
429            removeConnection(c);
430        }
431
432        @Override
433        public void onPostDialWait(Connection c, String remaining) {
434            String id = mIdByConnection.get(c);
435            Log.d(this, "Adapter onPostDialWait %s, %s", c, remaining);
436            mAdapter.onPostDialWait(id, remaining);
437        }
438
439        @Override
440        public void onRingbackRequested(Connection c, boolean ringback) {
441            String id = mIdByConnection.get(c);
442            Log.d(this, "Adapter onRingback %b", ringback);
443            mAdapter.setRingbackRequested(id, ringback);
444        }
445
446        @Override
447        public void onCallCapabilitiesChanged(Connection c, int capabilities) {
448            String id = mIdByConnection.get(c);
449            Log.d(this, "capabilities: parcelableconnection: %s",
450                    PhoneCapabilities.toString(capabilities));
451            mAdapter.setCallCapabilities(id, capabilities);
452        }
453
454        @Override
455        public void onVideoProviderChanged(Connection c, Connection.VideoProvider videoProvider) {
456            String id = mIdByConnection.get(c);
457            mAdapter.setVideoProvider(id, videoProvider);
458        }
459
460        @Override
461        public void onAudioModeIsVoipChanged(Connection c, boolean isVoip) {
462            String id = mIdByConnection.get(c);
463            mAdapter.setIsVoipAudioMode(id, isVoip);
464        }
465
466        @Override
467        public void onStatusHintsChanged(Connection c, StatusHints statusHints) {
468            String id = mIdByConnection.get(c);
469            mAdapter.setStatusHints(id, statusHints);
470        }
471
472        @Override
473        public void onConferenceableConnectionsChanged(
474                Connection connection, List<Connection> conferenceableConnections) {
475            mAdapter.setConferenceableConnections(
476                    mIdByConnection.get(connection),
477                    createConnectionIdList(conferenceableConnections));
478        }
479
480        @Override
481        public void onConferenceChanged(Connection connection, Conference conference) {
482            String id = mIdByConnection.get(connection);
483            if (id != null) {
484                String conferenceId = null;
485                if (conference != null) {
486                    conferenceId = mIdByConference.get(conference);
487                }
488                mAdapter.setIsConferenced(id, conferenceId);
489            }
490        }
491    };
492
493    /** {@inheritDoc} */
494    @Override
495    public final IBinder onBind(Intent intent) {
496        return mBinder;
497    }
498
499    /** {@inheritDoc} */
500    @Override
501    public boolean onUnbind(Intent intent) {
502        endAllConnections();
503        return super.onUnbind(intent);
504    }
505
506    /**
507     * This can be used by telecom to either create a new outgoing call or attach to an existing
508     * incoming call. In either case, telecom will cycle through a set of services and call
509     * createConnection util a connection service cancels the process or completes it successfully.
510     */
511    private void createConnection(
512            final PhoneAccountHandle callManagerAccount,
513            final String callId,
514            final ConnectionRequest request,
515            boolean isIncoming) {
516        Log.d(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " +
517                "isIncoming: %b", callManagerAccount, callId, request, isIncoming);
518
519        Connection connection = isIncoming
520                ? onCreateIncomingConnection(callManagerAccount, request)
521                : onCreateOutgoingConnection(callManagerAccount, request);
522        Log.d(this, "createConnection, connection: %s", connection);
523        if (connection == null) {
524            connection = Connection.createFailedConnection(
525                    new DisconnectCause(DisconnectCause.ERROR));
526        }
527
528        if (connection.getState() != Connection.STATE_DISCONNECTED) {
529            addConnection(callId, connection);
530        }
531
532        Uri address = connection.getAddress();
533        String number = address == null ? "null" : address.getSchemeSpecificPart();
534        Log.v(this, "createConnection, number: %s, state: %s, capabilities: %s",
535                Connection.toLogSafePhoneNumber(number),
536                Connection.stateToString(connection.getState()),
537                PhoneCapabilities.toString(connection.getCallCapabilities()));
538
539        Log.d(this, "createConnection, calling handleCreateConnectionSuccessful %s", callId);
540        mAdapter.handleCreateConnectionComplete(
541                callId,
542                request,
543                new ParcelableConnection(
544                        request.getAccountHandle(),
545                        connection.getState(),
546                        connection.getCallCapabilities(),
547                        connection.getAddress(),
548                        connection.getAddressPresentation(),
549                        connection.getCallerDisplayName(),
550                        connection.getCallerDisplayNamePresentation(),
551                        connection.getVideoProvider() == null ?
552                                null : connection.getVideoProvider().getInterface(),
553                        connection.getVideoState(),
554                        connection.isRingbackRequested(),
555                        connection.getAudioModeIsVoip(),
556                        connection.getStatusHints(),
557                        connection.getDisconnectCause(),
558                        createConnectionIdList(connection.getConferenceableConnections())));
559    }
560
561    private void abort(String callId) {
562        Log.d(this, "abort %s", callId);
563        findConnectionForAction(callId, "abort").onAbort();
564    }
565
566    private void answerVideo(String callId, int videoState) {
567        Log.d(this, "answerVideo %s", callId);
568        findConnectionForAction(callId, "answer").onAnswer(videoState);
569    }
570
571    private void answer(String callId) {
572        Log.d(this, "answer %s", callId);
573        findConnectionForAction(callId, "answer").onAnswer();
574    }
575
576    private void reject(String callId) {
577        Log.d(this, "reject %s", callId);
578        findConnectionForAction(callId, "reject").onReject();
579    }
580
581    private void disconnect(String callId) {
582        Log.d(this, "disconnect %s", callId);
583        if (mConnectionById.containsKey(callId)) {
584            findConnectionForAction(callId, "disconnect").onDisconnect();
585        } else {
586            findConferenceForAction(callId, "disconnect").onDisconnect();
587        }
588    }
589
590    private void hold(String callId) {
591        Log.d(this, "hold %s", callId);
592        if (mConnectionById.containsKey(callId)) {
593            findConnectionForAction(callId, "hold").onHold();
594        } else {
595            findConferenceForAction(callId, "hold").onHold();
596        }
597    }
598
599    private void unhold(String callId) {
600        Log.d(this, "unhold %s", callId);
601        if (mConnectionById.containsKey(callId)) {
602            findConnectionForAction(callId, "unhold").onUnhold();
603        } else {
604            findConferenceForAction(callId, "unhold").onUnhold();
605        }
606    }
607
608    private void onAudioStateChanged(String callId, AudioState audioState) {
609        Log.d(this, "onAudioStateChanged %s %s", callId, audioState);
610        if (mConnectionById.containsKey(callId)) {
611            findConnectionForAction(callId, "onAudioStateChanged").setAudioState(audioState);
612        } else {
613            findConferenceForAction(callId, "onAudioStateChanged").setAudioState(audioState);
614        }
615    }
616
617    private void playDtmfTone(String callId, char digit) {
618        Log.d(this, "playDtmfTone %s %c", callId, digit);
619        if (mConnectionById.containsKey(callId)) {
620            findConnectionForAction(callId, "playDtmfTone").onPlayDtmfTone(digit);
621        } else {
622            findConferenceForAction(callId, "playDtmfTone").onPlayDtmfTone(digit);
623        }
624    }
625
626    private void stopDtmfTone(String callId) {
627        Log.d(this, "stopDtmfTone %s", callId);
628        if (mConnectionById.containsKey(callId)) {
629            findConnectionForAction(callId, "stopDtmfTone").onStopDtmfTone();
630        } else {
631            findConferenceForAction(callId, "stopDtmfTone").onStopDtmfTone();
632        }
633    }
634
635    private void conference(String callId1, String callId2) {
636        Log.d(this, "conference %s, %s", callId1, callId2);
637
638        Connection connection1 = findConnectionForAction(callId1, "conference");
639        if (connection1 == getNullConnection()) {
640            Log.w(this, "Connection1 missing in conference request %s.", callId1);
641            return;
642        }
643
644        Connection connection2 = findConnectionForAction(callId2, "conference");
645        if (connection2 == getNullConnection()) {
646            Log.w(this, "Connection2 missing in conference request %s.", callId2);
647            return;
648        }
649
650        onConference(connection1, connection2);
651    }
652
653    private void splitFromConference(String callId) {
654        Log.d(this, "splitFromConference(%s)", callId);
655
656        Connection connection = findConnectionForAction(callId, "splitFromConference");
657        if (connection == getNullConnection()) {
658            Log.w(this, "Connection missing in conference request %s.", callId);
659            return;
660        }
661
662        Conference conference = connection.getConference();
663        if (conference != null) {
664            conference.onSeparate(connection);
665        }
666    }
667
668    private void mergeConference(String callId) {
669        Log.d(this, "mergeConference(%s)", callId);
670        Conference conference = findConferenceForAction(callId, "mergeConference");
671        if (conference != null) {
672            conference.onMerge();
673        }
674    }
675
676    private void swapConference(String callId) {
677        Log.d(this, "swapConference(%s)", callId);
678        Conference conference = findConferenceForAction(callId, "swapConference");
679        if (conference != null) {
680            conference.onSwap();
681        }
682    }
683
684    private void onPostDialContinue(String callId, boolean proceed) {
685        Log.d(this, "onPostDialContinue(%s)", callId);
686        findConnectionForAction(callId, "stopDtmfTone").onPostDialContinue(proceed);
687    }
688
689    private void onAdapterAttached() {
690        if (mAreAccountsInitialized) {
691            // No need to query again if we already did it.
692            return;
693        }
694
695        mAdapter.queryRemoteConnectionServices(new RemoteServiceCallback.Stub() {
696            @Override
697            public void onResult(
698                    final List<ComponentName> componentNames,
699                    final List<IBinder> services) {
700                mHandler.post(new Runnable() {
701                    @Override
702                    public void run() {
703                        for (int i = 0; i < componentNames.size() && i < services.size(); i++) {
704                            mRemoteConnectionManager.addConnectionService(
705                                    componentNames.get(i),
706                                    IConnectionService.Stub.asInterface(services.get(i)));
707                        }
708                        onAccountsInitialized();
709                        Log.d(this, "remote connection services found: " + services);
710                    }
711                });
712            }
713
714            @Override
715            public void onError() {
716                mHandler.post(new Runnable() {
717                    @Override
718                    public void run() {
719                        mAreAccountsInitialized = true;
720                    }
721                });
722            }
723        });
724    }
725
726    /**
727     * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an
728     * incoming request. This is used to attach to existing incoming calls.
729     *
730     * @param connectionManagerPhoneAccount See description at
731     *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
732     * @param request Details about the incoming call.
733     * @return The {@code Connection} object to satisfy this call, or {@code null} to
734     *         not handle the call.
735     */
736    public final RemoteConnection createRemoteIncomingConnection(
737            PhoneAccountHandle connectionManagerPhoneAccount,
738            ConnectionRequest request) {
739        return mRemoteConnectionManager.createRemoteConnection(
740                connectionManagerPhoneAccount, request, true);
741    }
742
743    /**
744     * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an
745     * outgoing request. This is used to initiate new outgoing calls.
746     *
747     * @param connectionManagerPhoneAccount See description at
748     *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
749     * @param request Details about the incoming call.
750     * @return The {@code Connection} object to satisfy this call, or {@code null} to
751     *         not handle the call.
752     */
753    public final RemoteConnection createRemoteOutgoingConnection(
754            PhoneAccountHandle connectionManagerPhoneAccount,
755            ConnectionRequest request) {
756        return mRemoteConnectionManager.createRemoteConnection(
757                connectionManagerPhoneAccount, request, false);
758    }
759
760    /**
761     * Adds two {@code RemoteConnection}s to some {@code RemoteConference}.
762     */
763    public final void conferenceRemoteConnections(
764            RemoteConnection a,
765            RemoteConnection b) {
766        mRemoteConnectionManager.conferenceRemoteConnections(a, b);
767    }
768
769    /**
770     * Adds a new conference call. When a conference call is created either as a result of an
771     * explicit request via {@link #onConference} or otherwise, the connection service should supply
772     * an instance of {@link Conference} by invoking this method. A conference call provided by this
773     * method will persist until {@link Conference#destroy} is invoked on the conference instance.
774     *
775     * @param conference The new conference object.
776     */
777    public final void addConference(Conference conference) {
778        String id = addConferenceInternal(conference);
779        if (id != null) {
780            List<String> connectionIds = new ArrayList<>(2);
781            for (Connection connection : conference.getConnections()) {
782                if (mIdByConnection.containsKey(connection)) {
783                    connectionIds.add(mIdByConnection.get(connection));
784                }
785            }
786            ParcelableConference parcelableConference = new ParcelableConference(
787                    conference.getPhoneAccountHandle(),
788                    conference.getState(),
789                    conference.getCapabilities(),
790                    connectionIds);
791            mAdapter.addConferenceCall(id, parcelableConference);
792
793            // Go through any child calls and set the parent.
794            for (Connection connection : conference.getConnections()) {
795                String connectionId = mIdByConnection.get(connection);
796                if (connectionId != null) {
797                    mAdapter.setIsConferenced(connectionId, id);
798                }
799            }
800        }
801    }
802
803    /**
804     * Returns all the active {@code Connection}s for which this {@code ConnectionService}
805     * has taken responsibility.
806     *
807     * @return A collection of {@code Connection}s created by this {@code ConnectionService}.
808     */
809    public final Collection<Connection> getAllConnections() {
810        return mConnectionById.values();
811    }
812
813    /**
814     * Create a {@code Connection} given an incoming request. This is used to attach to existing
815     * incoming calls.
816     *
817     * @param connectionManagerPhoneAccount See description at
818     *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
819     * @param request Details about the incoming call.
820     * @return The {@code Connection} object to satisfy this call, or {@code null} to
821     *         not handle the call.
822     */
823    public Connection onCreateIncomingConnection(
824            PhoneAccountHandle connectionManagerPhoneAccount,
825            ConnectionRequest request) {
826        return null;
827    }
828
829    /**
830     * Create a {@code Connection} given an outgoing request. This is used to initiate new
831     * outgoing calls.
832     *
833     * @param connectionManagerPhoneAccount The connection manager account to use for managing
834     *         this call.
835     *         <p>
836     *         If this parameter is not {@code null}, it means that this {@code ConnectionService}
837     *         has registered one or more {@code PhoneAccount}s having
838     *         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}. This parameter will contain
839     *         one of these {@code PhoneAccount}s, while the {@code request} will contain another
840     *         (usually but not always distinct) {@code PhoneAccount} to be used for actually
841     *         making the connection.
842     *         <p>
843     *         If this parameter is {@code null}, it means that this {@code ConnectionService} is
844     *         being asked to make a direct connection. The
845     *         {@link ConnectionRequest#getAccountHandle()} of parameter {@code request} will be
846     *         a {@code PhoneAccount} registered by this {@code ConnectionService} to use for
847     *         making the connection.
848     * @param request Details about the outgoing call.
849     * @return The {@code Connection} object to satisfy this call, or the result of an invocation
850     *         of {@link Connection#createFailedConnection(DisconnectCause)} to not handle the call.
851     */
852    public Connection onCreateOutgoingConnection(
853            PhoneAccountHandle connectionManagerPhoneAccount,
854            ConnectionRequest request) {
855        return null;
856    }
857
858    /**
859     * Conference two specified connections. Invoked when the user has made a request to merge the
860     * specified connections into a conference call. In response, the connection service should
861     * create an instance of {@link Conference} and pass it into {@link #addConference}.
862     *
863     * @param connection1 A connection to merge into a conference call.
864     * @param connection2 A connection to merge into a conference call.
865     */
866    public void onConference(Connection connection1, Connection connection2) {}
867
868    public void onRemoteConferenceAdded(RemoteConference conference) {}
869
870    /**
871     * @hide
872     */
873    public boolean containsConference(Conference conference) {
874        return mIdByConference.containsKey(conference);
875    }
876
877    /** {@hide} */
878    void addRemoteConference(RemoteConference remoteConference) {
879        onRemoteConferenceAdded(remoteConference);
880    }
881
882    private void onAccountsInitialized() {
883        mAreAccountsInitialized = true;
884        for (Runnable r : mPreInitializationConnectionRequests) {
885            r.run();
886        }
887        mPreInitializationConnectionRequests.clear();
888    }
889
890    private void addConnection(String callId, Connection connection) {
891        mConnectionById.put(callId, connection);
892        mIdByConnection.put(connection, callId);
893        connection.addConnectionListener(mConnectionListener);
894        connection.setConnectionService(this);
895    }
896
897    private void removeConnection(Connection connection) {
898        String id = mIdByConnection.get(connection);
899        connection.unsetConnectionService(this);
900        connection.removeConnectionListener(mConnectionListener);
901        mConnectionById.remove(mIdByConnection.get(connection));
902        mIdByConnection.remove(connection);
903        mAdapter.removeCall(id);
904    }
905
906    private String addConferenceInternal(Conference conference) {
907        if (mIdByConference.containsKey(conference)) {
908            Log.w(this, "Re-adding an existing conference: %s.", conference);
909        } else if (conference != null) {
910            String id = UUID.randomUUID().toString();
911            mConferenceById.put(id, conference);
912            mIdByConference.put(conference, id);
913            conference.addListener(mConferenceListener);
914            return id;
915        }
916
917        return null;
918    }
919
920    private void removeConference(Conference conference) {
921        if (mIdByConference.containsKey(conference)) {
922            conference.removeListener(mConferenceListener);
923
924            String id = mIdByConference.get(conference);
925            mConferenceById.remove(id);
926            mIdByConference.remove(conference);
927            mAdapter.removeCall(id);
928        }
929    }
930
931    private Connection findConnectionForAction(String callId, String action) {
932        if (mConnectionById.containsKey(callId)) {
933            return mConnectionById.get(callId);
934        }
935        Log.w(this, "%s - Cannot find Connection %s", action, callId);
936        return getNullConnection();
937    }
938
939    static synchronized Connection getNullConnection() {
940        if (sNullConnection == null) {
941            sNullConnection = new Connection() {};
942        }
943        return sNullConnection;
944    }
945
946    private Conference findConferenceForAction(String conferenceId, String action) {
947        if (mConferenceById.containsKey(conferenceId)) {
948            return mConferenceById.get(conferenceId);
949        }
950        Log.w(this, "%s - Cannot find conference %s", action, conferenceId);
951        return getNullConference();
952    }
953
954    private List<String> createConnectionIdList(List<Connection> connections) {
955        List<String> ids = new ArrayList<>();
956        for (Connection c : connections) {
957            if (mIdByConnection.containsKey(c)) {
958                ids.add(mIdByConnection.get(c));
959            }
960        }
961        Collections.sort(ids);
962        return ids;
963    }
964
965    private Conference getNullConference() {
966        if (sNullConference == null) {
967            sNullConference = new Conference(null) {};
968        }
969        return sNullConference;
970    }
971
972    private void endAllConnections() {
973        // Unbound from telecomm.  We should end all connections and conferences.
974        for (Connection connection : mIdByConnection.keySet()) {
975            // only operate on top-level calls. Conference calls will be removed on their own.
976            if (connection.getConference() == null) {
977                connection.onDisconnect();
978            }
979        }
980        for (Conference conference : mIdByConference.keySet()) {
981            conference.onDisconnect();
982        }
983    }
984}
985