ConnectionService.java revision 7f3d41fd124dd7c4a8b72c1d48df08a8ee7209ec
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 %d %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        findConnectionForAction(callId, "onAudioStateChanged").setAudioState(audioState);
611    }
612
613    private void playDtmfTone(String callId, char digit) {
614        Log.d(this, "playDtmfTone %s %c", callId, digit);
615        findConnectionForAction(callId, "playDtmfTone").onPlayDtmfTone(digit);
616    }
617
618    private void stopDtmfTone(String callId) {
619        Log.d(this, "stopDtmfTone %s", callId);
620        findConnectionForAction(callId, "stopDtmfTone").onStopDtmfTone();
621    }
622
623    private void conference(String callId1, String callId2) {
624        Log.d(this, "conference %s, %s", callId1, callId2);
625
626        Connection connection1 = findConnectionForAction(callId1, "conference");
627        if (connection1 == getNullConnection()) {
628            Log.w(this, "Connection1 missing in conference request %s.", callId1);
629            return;
630        }
631
632        Connection connection2 = findConnectionForAction(callId2, "conference");
633        if (connection2 == getNullConnection()) {
634            Log.w(this, "Connection2 missing in conference request %s.", callId2);
635            return;
636        }
637
638        onConference(connection1, connection2);
639    }
640
641    private void splitFromConference(String callId) {
642        Log.d(this, "splitFromConference(%s)", callId);
643
644        Connection connection = findConnectionForAction(callId, "splitFromConference");
645        if (connection == getNullConnection()) {
646            Log.w(this, "Connection missing in conference request %s.", callId);
647            return;
648        }
649
650        Conference conference = connection.getConference();
651        if (conference != null) {
652            conference.onSeparate(connection);
653        }
654    }
655
656    private void mergeConference(String callId) {
657        Log.d(this, "mergeConference(%s)", callId);
658        Conference conference = findConferenceForAction(callId, "mergeConference");
659        if (conference != null) {
660            conference.onMerge();
661        }
662    }
663
664    private void swapConference(String callId) {
665        Log.d(this, "swapConference(%s)", callId);
666        Conference conference = findConferenceForAction(callId, "swapConference");
667        if (conference != null) {
668            conference.onSwap();
669        }
670    }
671
672    private void onPostDialContinue(String callId, boolean proceed) {
673        Log.d(this, "onPostDialContinue(%s)", callId);
674        findConnectionForAction(callId, "stopDtmfTone").onPostDialContinue(proceed);
675    }
676
677    private void onAdapterAttached() {
678        if (mAreAccountsInitialized) {
679            // No need to query again if we already did it.
680            return;
681        }
682
683        mAdapter.queryRemoteConnectionServices(new RemoteServiceCallback.Stub() {
684            @Override
685            public void onResult(
686                    final List<ComponentName> componentNames,
687                    final List<IBinder> services) {
688                mHandler.post(new Runnable() {
689                    @Override
690                    public void run() {
691                        for (int i = 0; i < componentNames.size() && i < services.size(); i++) {
692                            mRemoteConnectionManager.addConnectionService(
693                                    componentNames.get(i),
694                                    IConnectionService.Stub.asInterface(services.get(i)));
695                        }
696                        onAccountsInitialized();
697                        Log.d(this, "remote connection services found: " + services);
698                    }
699                });
700            }
701
702            @Override
703            public void onError() {
704                mHandler.post(new Runnable() {
705                    @Override
706                    public void run() {
707                        mAreAccountsInitialized = true;
708                    }
709                });
710            }
711        });
712    }
713
714    /**
715     * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an
716     * incoming request. This is used to attach to existing incoming calls.
717     *
718     * @param connectionManagerPhoneAccount See description at
719     *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
720     * @param request Details about the incoming call.
721     * @return The {@code Connection} object to satisfy this call, or {@code null} to
722     *         not handle the call.
723     */
724    public final RemoteConnection createRemoteIncomingConnection(
725            PhoneAccountHandle connectionManagerPhoneAccount,
726            ConnectionRequest request) {
727        return mRemoteConnectionManager.createRemoteConnection(
728                connectionManagerPhoneAccount, request, true);
729    }
730
731    /**
732     * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an
733     * outgoing request. This is used to initiate new outgoing calls.
734     *
735     * @param connectionManagerPhoneAccount See description at
736     *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
737     * @param request Details about the incoming call.
738     * @return The {@code Connection} object to satisfy this call, or {@code null} to
739     *         not handle the call.
740     */
741    public final RemoteConnection createRemoteOutgoingConnection(
742            PhoneAccountHandle connectionManagerPhoneAccount,
743            ConnectionRequest request) {
744        return mRemoteConnectionManager.createRemoteConnection(
745                connectionManagerPhoneAccount, request, false);
746    }
747
748    /**
749     * Adds two {@code RemoteConnection}s to some {@code RemoteConference}.
750     */
751    public final void conferenceRemoteConnections(
752            RemoteConnection a,
753            RemoteConnection b) {
754        mRemoteConnectionManager.conferenceRemoteConnections(a, b);
755    }
756
757    /**
758     * Adds a new conference call. When a conference call is created either as a result of an
759     * explicit request via {@link #onConference} or otherwise, the connection service should supply
760     * an instance of {@link Conference} by invoking this method. A conference call provided by this
761     * method will persist until {@link Conference#destroy} is invoked on the conference instance.
762     *
763     * @param conference The new conference object.
764     */
765    public final void addConference(Conference conference) {
766        String id = addConferenceInternal(conference);
767        if (id != null) {
768            List<String> connectionIds = new ArrayList<>(2);
769            for (Connection connection : conference.getConnections()) {
770                if (mIdByConnection.containsKey(connection)) {
771                    connectionIds.add(mIdByConnection.get(connection));
772                }
773            }
774            ParcelableConference parcelableConference = new ParcelableConference(
775                    conference.getPhoneAccountHandle(),
776                    conference.getState(),
777                    conference.getCapabilities(),
778                    connectionIds);
779            mAdapter.addConferenceCall(id, parcelableConference);
780
781            // Go through any child calls and set the parent.
782            for (Connection connection : conference.getConnections()) {
783                String connectionId = mIdByConnection.get(connection);
784                if (connectionId != null) {
785                    mAdapter.setIsConferenced(connectionId, id);
786                }
787            }
788        }
789    }
790
791    /**
792     * Returns all the active {@code Connection}s for which this {@code ConnectionService}
793     * has taken responsibility.
794     *
795     * @return A collection of {@code Connection}s created by this {@code ConnectionService}.
796     */
797    public final Collection<Connection> getAllConnections() {
798        return mConnectionById.values();
799    }
800
801    /**
802     * Create a {@code Connection} given an incoming request. This is used to attach to existing
803     * incoming calls.
804     *
805     * @param connectionManagerPhoneAccount See description at
806     *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
807     * @param request Details about the incoming call.
808     * @return The {@code Connection} object to satisfy this call, or {@code null} to
809     *         not handle the call.
810     */
811    public Connection onCreateIncomingConnection(
812            PhoneAccountHandle connectionManagerPhoneAccount,
813            ConnectionRequest request) {
814        return null;
815    }
816
817    /**
818     * Create a {@code Connection} given an outgoing request. This is used to initiate new
819     * outgoing calls.
820     *
821     * @param connectionManagerPhoneAccount The connection manager account to use for managing
822     *         this call.
823     *         <p>
824     *         If this parameter is not {@code null}, it means that this {@code ConnectionService}
825     *         has registered one or more {@code PhoneAccount}s having
826     *         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}. This parameter will contain
827     *         one of these {@code PhoneAccount}s, while the {@code request} will contain another
828     *         (usually but not always distinct) {@code PhoneAccount} to be used for actually
829     *         making the connection.
830     *         <p>
831     *         If this parameter is {@code null}, it means that this {@code ConnectionService} is
832     *         being asked to make a direct connection. The
833     *         {@link ConnectionRequest#getAccountHandle()} of parameter {@code request} will be
834     *         a {@code PhoneAccount} registered by this {@code ConnectionService} to use for
835     *         making the connection.
836     * @param request Details about the outgoing call.
837     * @return The {@code Connection} object to satisfy this call, or the result of an invocation
838     *         of {@link Connection#createFailedConnection(DisconnectCause)} to not handle the call.
839     */
840    public Connection onCreateOutgoingConnection(
841            PhoneAccountHandle connectionManagerPhoneAccount,
842            ConnectionRequest request) {
843        return null;
844    }
845
846    /**
847     * Conference two specified connections. Invoked when the user has made a request to merge the
848     * specified connections into a conference call. In response, the connection service should
849     * create an instance of {@link Conference} and pass it into {@link #addConference}.
850     *
851     * @param connection1 A connection to merge into a conference call.
852     * @param connection2 A connection to merge into a conference call.
853     */
854    public void onConference(Connection connection1, Connection connection2) {}
855
856    public void onRemoteConferenceAdded(RemoteConference conference) {}
857
858    /**
859     * @hide
860     */
861    public boolean containsConference(Conference conference) {
862        return mIdByConference.containsKey(conference);
863    }
864
865    /** {@hide} */
866    void addRemoteConference(RemoteConference remoteConference) {
867        onRemoteConferenceAdded(remoteConference);
868    }
869
870    private void onAccountsInitialized() {
871        mAreAccountsInitialized = true;
872        for (Runnable r : mPreInitializationConnectionRequests) {
873            r.run();
874        }
875        mPreInitializationConnectionRequests.clear();
876    }
877
878    private void addConnection(String callId, Connection connection) {
879        mConnectionById.put(callId, connection);
880        mIdByConnection.put(connection, callId);
881        connection.addConnectionListener(mConnectionListener);
882        connection.setConnectionService(this);
883    }
884
885    private void removeConnection(Connection connection) {
886        String id = mIdByConnection.get(connection);
887        connection.unsetConnectionService(this);
888        connection.removeConnectionListener(mConnectionListener);
889        mConnectionById.remove(mIdByConnection.get(connection));
890        mIdByConnection.remove(connection);
891        mAdapter.removeCall(id);
892    }
893
894    private String addConferenceInternal(Conference conference) {
895        if (mIdByConference.containsKey(conference)) {
896            Log.w(this, "Re-adding an existing conference: %s.", conference);
897        } else if (conference != null) {
898            String id = UUID.randomUUID().toString();
899            mConferenceById.put(id, conference);
900            mIdByConference.put(conference, id);
901            conference.addListener(mConferenceListener);
902            return id;
903        }
904
905        return null;
906    }
907
908    private void removeConference(Conference conference) {
909        if (mIdByConference.containsKey(conference)) {
910            conference.removeListener(mConferenceListener);
911
912            String id = mIdByConference.get(conference);
913            mConferenceById.remove(id);
914            mIdByConference.remove(conference);
915            mAdapter.removeCall(id);
916        }
917    }
918
919    private Connection findConnectionForAction(String callId, String action) {
920        if (mConnectionById.containsKey(callId)) {
921            return mConnectionById.get(callId);
922        }
923        Log.w(this, "%s - Cannot find Connection %s", action, callId);
924        return getNullConnection();
925    }
926
927    static synchronized Connection getNullConnection() {
928        if (sNullConnection == null) {
929            sNullConnection = new Connection() {};
930        }
931        return sNullConnection;
932    }
933
934    private Conference findConferenceForAction(String conferenceId, String action) {
935        if (mConferenceById.containsKey(conferenceId)) {
936            return mConferenceById.get(conferenceId);
937        }
938        Log.w(this, "%s - Cannot find conference %s", action, conferenceId);
939        return getNullConference();
940    }
941
942    private List<String> createConnectionIdList(List<Connection> connections) {
943        List<String> ids = new ArrayList<>();
944        for (Connection c : connections) {
945            if (mIdByConnection.containsKey(c)) {
946                ids.add(mIdByConnection.get(c));
947            }
948        }
949        Collections.sort(ids);
950        return ids;
951    }
952
953    private Conference getNullConference() {
954        if (sNullConference == null) {
955            sNullConference = new Conference(null) {};
956        }
957        return sNullConference;
958    }
959
960    private void endAllConnections() {
961        // Unbound from telecomm.  We should end all connections and conferences.
962        for (Connection connection : mIdByConnection.keySet()) {
963            // only operate on top-level calls. Conference calls will be removed on their own.
964            if (connection.getConference() == null) {
965                connection.onDisconnect();
966            }
967        }
968        for (Conference conference : mIdByConference.keySet()) {
969            conference.onDisconnect();
970        }
971    }
972}
973