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