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