ConnectionService.java revision 4a57b9b59b74c97e559a301af0add13cd4c3331c
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     * Adds a connection created by the {@link ConnectionService} and informs telecom of the new
826     * connection.
827     *
828     * @param phoneAccountHandle The phone account handle for the connection.
829     * @param connection The connection to add.
830     */
831    public final void addExistingConnection(PhoneAccountHandle phoneAccountHandle,
832            Connection connection) {
833
834        String id = addExistingConnectionInternal(connection);
835        if (id != null) {
836            List<String> emptyList = new ArrayList<>(0);
837
838            ParcelableConnection parcelableConnection = new ParcelableConnection(
839                    phoneAccountHandle,
840                    connection.getState(),
841                    connection.getCallCapabilities(),
842                    connection.getAddress(),
843                    connection.getAddressPresentation(),
844                    connection.getCallerDisplayName(),
845                    connection.getCallerDisplayNamePresentation(),
846                    connection.getVideoProvider() == null ?
847                            null : connection.getVideoProvider().getInterface(),
848                    connection.getVideoState(),
849                    connection.isRingbackRequested(),
850                    connection.getAudioModeIsVoip(),
851                    connection.getStatusHints(),
852                    connection.getDisconnectCause(),
853                    emptyList);
854            mAdapter.addExistingConnection(id, parcelableConnection);
855        }
856    }
857
858    /**
859     * Returns all the active {@code Connection}s for which this {@code ConnectionService}
860     * has taken responsibility.
861     *
862     * @return A collection of {@code Connection}s created by this {@code ConnectionService}.
863     */
864    public final Collection<Connection> getAllConnections() {
865        return mConnectionById.values();
866    }
867
868    /**
869     * Create a {@code Connection} given an incoming request. This is used to attach to existing
870     * incoming calls.
871     *
872     * @param connectionManagerPhoneAccount See description at
873     *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
874     * @param request Details about the incoming call.
875     * @return The {@code Connection} object to satisfy this call, or {@code null} to
876     *         not handle the call.
877     */
878    public Connection onCreateIncomingConnection(
879            PhoneAccountHandle connectionManagerPhoneAccount,
880            ConnectionRequest request) {
881        return null;
882    }
883
884    /**
885     * Create a {@code Connection} given an outgoing request. This is used to initiate new
886     * outgoing calls.
887     *
888     * @param connectionManagerPhoneAccount The connection manager account to use for managing
889     *         this call.
890     *         <p>
891     *         If this parameter is not {@code null}, it means that this {@code ConnectionService}
892     *         has registered one or more {@code PhoneAccount}s having
893     *         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}. This parameter will contain
894     *         one of these {@code PhoneAccount}s, while the {@code request} will contain another
895     *         (usually but not always distinct) {@code PhoneAccount} to be used for actually
896     *         making the connection.
897     *         <p>
898     *         If this parameter is {@code null}, it means that this {@code ConnectionService} is
899     *         being asked to make a direct connection. The
900     *         {@link ConnectionRequest#getAccountHandle()} of parameter {@code request} will be
901     *         a {@code PhoneAccount} registered by this {@code ConnectionService} to use for
902     *         making the connection.
903     * @param request Details about the outgoing call.
904     * @return The {@code Connection} object to satisfy this call, or the result of an invocation
905     *         of {@link Connection#createFailedConnection(DisconnectCause)} to not handle the call.
906     */
907    public Connection onCreateOutgoingConnection(
908            PhoneAccountHandle connectionManagerPhoneAccount,
909            ConnectionRequest request) {
910        return null;
911    }
912
913    /**
914     * Create a {@code Connection} for a new unknown call. An unknown call is a call originating
915     * from the ConnectionService that was neither a user-initiated outgoing call, nor an incoming
916     * call created using
917     * {@code TelecomManager#addNewIncomingCall(PhoneAccountHandle, android.os.Bundle)}.
918     *
919     * @param connectionManagerPhoneAccount
920     * @param request
921     * @return
922     *
923     * @hide
924     */
925    public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount,
926            ConnectionRequest request) {
927       return null;
928    }
929
930    /**
931     * Conference two specified connections. Invoked when the user has made a request to merge the
932     * specified connections into a conference call. In response, the connection service should
933     * create an instance of {@link Conference} and pass it into {@link #addConference}.
934     *
935     * @param connection1 A connection to merge into a conference call.
936     * @param connection2 A connection to merge into a conference call.
937     */
938    public void onConference(Connection connection1, Connection connection2) {}
939
940    public void onRemoteConferenceAdded(RemoteConference conference) {}
941
942    /**
943     * Called when an existing connection is added remotely.
944     * @param connection The existing connection which was added.
945     */
946    public void onRemoteExistingConnectionAdded(RemoteConnection connection) {}
947
948    /**
949     * @hide
950     */
951    public boolean containsConference(Conference conference) {
952        return mIdByConference.containsKey(conference);
953    }
954
955    /** {@hide} */
956    void addRemoteConference(RemoteConference remoteConference) {
957        onRemoteConferenceAdded(remoteConference);
958    }
959
960    /** {@hide} */
961    void addRemoteExistingConnection(RemoteConnection remoteConnection) {
962        onRemoteExistingConnectionAdded(remoteConnection);
963    }
964
965    private void onAccountsInitialized() {
966        mAreAccountsInitialized = true;
967        for (Runnable r : mPreInitializationConnectionRequests) {
968            r.run();
969        }
970        mPreInitializationConnectionRequests.clear();
971    }
972
973    /**
974     * Adds an existing connection to the list of connections, identified by a new UUID.
975     *
976     * @param connection The connection.
977     * @return The UUID of the connection (e.g. the call-id).
978     */
979    private String addExistingConnectionInternal(Connection connection) {
980        String id = UUID.randomUUID().toString();
981        addConnection(id, connection);
982        return id;
983    }
984
985    private void addConnection(String callId, Connection connection) {
986        mConnectionById.put(callId, connection);
987        mIdByConnection.put(connection, callId);
988        connection.addConnectionListener(mConnectionListener);
989        connection.setConnectionService(this);
990    }
991
992    private void removeConnection(Connection connection) {
993        String id = mIdByConnection.get(connection);
994        connection.unsetConnectionService(this);
995        connection.removeConnectionListener(mConnectionListener);
996        mConnectionById.remove(mIdByConnection.get(connection));
997        mIdByConnection.remove(connection);
998        mAdapter.removeCall(id);
999    }
1000
1001    private String addConferenceInternal(Conference conference) {
1002        if (mIdByConference.containsKey(conference)) {
1003            Log.w(this, "Re-adding an existing conference: %s.", conference);
1004        } else if (conference != null) {
1005            String id = UUID.randomUUID().toString();
1006            mConferenceById.put(id, conference);
1007            mIdByConference.put(conference, id);
1008            conference.addListener(mConferenceListener);
1009            return id;
1010        }
1011
1012        return null;
1013    }
1014
1015    private void removeConference(Conference conference) {
1016        if (mIdByConference.containsKey(conference)) {
1017            conference.removeListener(mConferenceListener);
1018
1019            String id = mIdByConference.get(conference);
1020            mConferenceById.remove(id);
1021            mIdByConference.remove(conference);
1022            mAdapter.removeCall(id);
1023        }
1024    }
1025
1026    private Connection findConnectionForAction(String callId, String action) {
1027        if (mConnectionById.containsKey(callId)) {
1028            return mConnectionById.get(callId);
1029        }
1030        Log.w(this, "%s - Cannot find Connection %s", action, callId);
1031        return getNullConnection();
1032    }
1033
1034    static synchronized Connection getNullConnection() {
1035        if (sNullConnection == null) {
1036            sNullConnection = new Connection() {};
1037        }
1038        return sNullConnection;
1039    }
1040
1041    private Conference findConferenceForAction(String conferenceId, String action) {
1042        if (mConferenceById.containsKey(conferenceId)) {
1043            return mConferenceById.get(conferenceId);
1044        }
1045        Log.w(this, "%s - Cannot find conference %s", action, conferenceId);
1046        return getNullConference();
1047    }
1048
1049    private List<String> createConnectionIdList(List<Connection> connections) {
1050        List<String> ids = new ArrayList<>();
1051        for (Connection c : connections) {
1052            if (mIdByConnection.containsKey(c)) {
1053                ids.add(mIdByConnection.get(c));
1054            }
1055        }
1056        Collections.sort(ids);
1057        return ids;
1058    }
1059
1060    private Conference getNullConference() {
1061        if (sNullConference == null) {
1062            sNullConference = new Conference(null) {};
1063        }
1064        return sNullConference;
1065    }
1066
1067    private void endAllConnections() {
1068        // Unbound from telecomm.  We should end all connections and conferences.
1069        for (Connection connection : mIdByConnection.keySet()) {
1070            // only operate on top-level calls. Conference calls will be removed on their own.
1071            if (connection.getConference() == null) {
1072                connection.onDisconnect();
1073            }
1074        }
1075        for (Conference conference : mIdByConference.keySet()) {
1076            conference.onDisconnect();
1077        }
1078    }
1079}
1080