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