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