ConnectionService.java revision f50354363c2bace924c6aeea5746b3c690a7108e
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.Bundle;
25import android.os.Handler;
26import android.os.IBinder;
27import android.os.Looper;
28import android.os.Message;
29import android.telecom.Logging.Session;
30
31import com.android.internal.os.SomeArgs;
32import com.android.internal.telecom.IConnectionService;
33import com.android.internal.telecom.IConnectionServiceAdapter;
34import com.android.internal.telecom.RemoteServiceCallback;
35
36import java.util.ArrayList;
37import java.util.Collection;
38import java.util.Collections;
39import java.util.List;
40import java.util.Map;
41import java.util.UUID;
42import java.util.concurrent.ConcurrentHashMap;
43
44/**
45 * An abstract service that should be implemented by any apps which either:
46 * <ol>
47 *     <li>Can make phone calls (VoIP or otherwise) and want those calls to be integrated into the
48 *     built-in phone app.  Referred to as a <b>system managed</b> {@link ConnectionService}.</li>
49 *     <li>Are a standalone calling app and don't want their calls to be integrated into the
50 *     built-in phone app.  Referred to as a <b>self managed</b> {@link ConnectionService}.</li>
51 * </ol>
52 * Once implemented, the {@link ConnectionService} needs to take the following steps so that Telecom
53 * will bind to it:
54 * <p>
55 * 1. <i>Registration in AndroidManifest.xml</i>
56 * <br/>
57 * <pre>
58 * &lt;service android:name="com.example.package.MyConnectionService"
59 *    android:label="@string/some_label_for_my_connection_service"
60 *    android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"&gt;
61 *  &lt;intent-filter&gt;
62 *   &lt;action android:name="android.telecom.ConnectionService" /&gt;
63 *  &lt;/intent-filter&gt;
64 * &lt;/service&gt;
65 * </pre>
66 * <p>
67 * 2. <i> Registration of {@link PhoneAccount} with {@link TelecomManager}.</i>
68 * <br/>
69 * See {@link PhoneAccount} and {@link TelecomManager#registerPhoneAccount} for more information.
70 * <p>
71 * System managed {@link ConnectionService}s must be enabled by the user in the phone app settings
72 * before Telecom will bind to them.  Self-manged {@link ConnectionService}s must be granted the
73 * appropriate permission before Telecom will bind to them.
74 * <p>
75 * Once registered and enabled by the user in the phone app settings or granted permission, telecom
76 * will bind to a {@link ConnectionService} implementation when it wants that
77 * {@link ConnectionService} to place a call or the service has indicated that is has an incoming
78 * call through {@link TelecomManager#addNewIncomingCall}. The {@link ConnectionService} can then
79 * expect a call to {@link #onCreateIncomingConnection} or {@link #onCreateOutgoingConnection}
80 * wherein it should provide a new instance of a {@link Connection} object.  It is through this
81 * {@link Connection} object that telecom receives state updates and the {@link ConnectionService}
82 * receives call-commands such as answer, reject, hold and disconnect.
83 * <p>
84 * When there are no more live calls, telecom will unbind from the {@link ConnectionService}.
85 */
86public abstract class ConnectionService extends Service {
87    /**
88     * The {@link Intent} that must be declared as handled by the service.
89     */
90    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
91    public static final String SERVICE_INTERFACE = "android.telecom.ConnectionService";
92
93    // Flag controlling whether PII is emitted into the logs
94    private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG);
95
96    // Session Definitions
97    private static final String SESSION_HANDLER = "H.";
98    private static final String SESSION_ADD_CS_ADAPTER = "CS.aCSA";
99    private static final String SESSION_REMOVE_CS_ADAPTER = "CS.rCSA";
100    private static final String SESSION_CREATE_CONN = "CS.crCo";
101    private static final String SESSION_ABORT = "CS.ab";
102    private static final String SESSION_ANSWER = "CS.an";
103    private static final String SESSION_ANSWER_VIDEO = "CS.anV";
104    private static final String SESSION_REJECT = "CS.r";
105    private static final String SESSION_REJECT_MESSAGE = "CS.rWM";
106    private static final String SESSION_SILENCE = "CS.s";
107    private static final String SESSION_DISCONNECT = "CS.d";
108    private static final String SESSION_HOLD = "CS.h";
109    private static final String SESSION_UNHOLD = "CS.u";
110    private static final String SESSION_CALL_AUDIO_SC = "CS.cASC";
111    private static final String SESSION_PLAY_DTMF = "CS.pDT";
112    private static final String SESSION_STOP_DTMF = "CS.sDT";
113    private static final String SESSION_CONFERENCE = "CS.c";
114    private static final String SESSION_SPLIT_CONFERENCE = "CS.sFC";
115    private static final String SESSION_MERGE_CONFERENCE = "CS.mC";
116    private static final String SESSION_SWAP_CONFERENCE = "CS.sC";
117    private static final String SESSION_POST_DIAL_CONT = "CS.oPDC";
118    private static final String SESSION_PULL_EXTERNAL_CALL = "CS.pEC";
119    private static final String SESSION_SEND_CALL_EVENT = "CS.sCE";
120    private static final String SESSION_EXTRAS_CHANGED = "CS.oEC";
121
122    private static final int MSG_ADD_CONNECTION_SERVICE_ADAPTER = 1;
123    private static final int MSG_CREATE_CONNECTION = 2;
124    private static final int MSG_ABORT = 3;
125    private static final int MSG_ANSWER = 4;
126    private static final int MSG_REJECT = 5;
127    private static final int MSG_DISCONNECT = 6;
128    private static final int MSG_HOLD = 7;
129    private static final int MSG_UNHOLD = 8;
130    private static final int MSG_ON_CALL_AUDIO_STATE_CHANGED = 9;
131    private static final int MSG_PLAY_DTMF_TONE = 10;
132    private static final int MSG_STOP_DTMF_TONE = 11;
133    private static final int MSG_CONFERENCE = 12;
134    private static final int MSG_SPLIT_FROM_CONFERENCE = 13;
135    private static final int MSG_ON_POST_DIAL_CONTINUE = 14;
136    private static final int MSG_REMOVE_CONNECTION_SERVICE_ADAPTER = 16;
137    private static final int MSG_ANSWER_VIDEO = 17;
138    private static final int MSG_MERGE_CONFERENCE = 18;
139    private static final int MSG_SWAP_CONFERENCE = 19;
140    private static final int MSG_REJECT_WITH_MESSAGE = 20;
141    private static final int MSG_SILENCE = 21;
142    private static final int MSG_PULL_EXTERNAL_CALL = 22;
143    private static final int MSG_SEND_CALL_EVENT = 23;
144    private static final int MSG_ON_EXTRAS_CHANGED = 24;
145
146    private static Connection sNullConnection;
147
148    private final Map<String, Connection> mConnectionById = new ConcurrentHashMap<>();
149    private final Map<Connection, String> mIdByConnection = new ConcurrentHashMap<>();
150    private final Map<String, Conference> mConferenceById = new ConcurrentHashMap<>();
151    private final Map<Conference, String> mIdByConference = new ConcurrentHashMap<>();
152    private final RemoteConnectionManager mRemoteConnectionManager =
153            new RemoteConnectionManager(this);
154    private final List<Runnable> mPreInitializationConnectionRequests = new ArrayList<>();
155    private final ConnectionServiceAdapter mAdapter = new ConnectionServiceAdapter();
156
157    private boolean mAreAccountsInitialized = false;
158    private Conference sNullConference;
159    private Object mIdSyncRoot = new Object();
160    private int mId = 0;
161
162    private final IBinder mBinder = new IConnectionService.Stub() {
163        @Override
164        public void addConnectionServiceAdapter(IConnectionServiceAdapter adapter,
165                Session.Info sessionInfo) {
166            Log.startSession(sessionInfo, SESSION_ADD_CS_ADAPTER);
167            try {
168                SomeArgs args = SomeArgs.obtain();
169                args.arg1 = adapter;
170                args.arg2 = Log.createSubsession();
171                mHandler.obtainMessage(MSG_ADD_CONNECTION_SERVICE_ADAPTER, args).sendToTarget();
172            } finally {
173                Log.endSession();
174            }
175        }
176
177        public void removeConnectionServiceAdapter(IConnectionServiceAdapter adapter,
178                Session.Info sessionInfo) {
179            Log.startSession(sessionInfo, SESSION_REMOVE_CS_ADAPTER);
180            try {
181                SomeArgs args = SomeArgs.obtain();
182                args.arg1 = adapter;
183                args.arg2 = Log.createSubsession();
184                mHandler.obtainMessage(MSG_REMOVE_CONNECTION_SERVICE_ADAPTER, args).sendToTarget();
185            } finally {
186                Log.endSession();
187            }
188        }
189
190        @Override
191        public void createConnection(
192                PhoneAccountHandle connectionManagerPhoneAccount,
193                String id,
194                ConnectionRequest request,
195                boolean isIncoming,
196                boolean isUnknown,
197                Session.Info sessionInfo) {
198            Log.startSession(sessionInfo, SESSION_CREATE_CONN);
199            try {
200                SomeArgs args = SomeArgs.obtain();
201                args.arg1 = connectionManagerPhoneAccount;
202                args.arg2 = id;
203                args.arg3 = request;
204                args.arg4 = Log.createSubsession();
205                args.argi1 = isIncoming ? 1 : 0;
206                args.argi2 = isUnknown ? 1 : 0;
207                mHandler.obtainMessage(MSG_CREATE_CONNECTION, args).sendToTarget();
208            } finally {
209                Log.endSession();
210            }
211        }
212
213        @Override
214        public void abort(String callId, Session.Info sessionInfo) {
215            Log.startSession(sessionInfo, SESSION_ABORT);
216            try {
217                SomeArgs args = SomeArgs.obtain();
218                args.arg1 = callId;
219                args.arg2 = Log.createSubsession();
220                mHandler.obtainMessage(MSG_ABORT, args).sendToTarget();
221            } finally {
222                Log.endSession();
223            }
224        }
225
226        @Override
227        public void answerVideo(String callId, int videoState, Session.Info sessionInfo) {
228            Log.startSession(sessionInfo, SESSION_ANSWER_VIDEO);
229            try {
230                SomeArgs args = SomeArgs.obtain();
231                args.arg1 = callId;
232                args.arg2 = Log.createSubsession();
233                args.argi1 = videoState;
234                mHandler.obtainMessage(MSG_ANSWER_VIDEO, args).sendToTarget();
235            } finally {
236                Log.endSession();
237            }
238        }
239
240        @Override
241        public void answer(String callId, Session.Info sessionInfo) {
242            Log.startSession(sessionInfo, SESSION_ANSWER);
243            try {
244                SomeArgs args = SomeArgs.obtain();
245                args.arg1 = callId;
246                args.arg2 = Log.createSubsession();
247                mHandler.obtainMessage(MSG_ANSWER, args).sendToTarget();
248            } finally {
249                Log.endSession();
250            }
251        }
252
253        @Override
254        public void reject(String callId, Session.Info sessionInfo) {
255            Log.startSession(sessionInfo, SESSION_REJECT);
256            try {
257                SomeArgs args = SomeArgs.obtain();
258                args.arg1 = callId;
259                args.arg2 = Log.createSubsession();
260                mHandler.obtainMessage(MSG_REJECT, args).sendToTarget();
261            } finally {
262                Log.endSession();
263            }
264        }
265
266        @Override
267        public void rejectWithMessage(String callId, String message, Session.Info sessionInfo) {
268            Log.startSession(sessionInfo, SESSION_REJECT_MESSAGE);
269            try {
270                SomeArgs args = SomeArgs.obtain();
271                args.arg1 = callId;
272                args.arg2 = message;
273                args.arg3 = Log.createSubsession();
274                mHandler.obtainMessage(MSG_REJECT_WITH_MESSAGE, args).sendToTarget();
275            } finally {
276                Log.endSession();
277            }
278        }
279
280        @Override
281        public void silence(String callId, Session.Info sessionInfo) {
282            Log.startSession(sessionInfo, SESSION_SILENCE);
283            try {
284                SomeArgs args = SomeArgs.obtain();
285                args.arg1 = callId;
286                args.arg2 = Log.createSubsession();
287                mHandler.obtainMessage(MSG_SILENCE, args).sendToTarget();
288            } finally {
289                Log.endSession();
290            }
291        }
292
293        @Override
294        public void disconnect(String callId, Session.Info sessionInfo) {
295            Log.startSession(sessionInfo, SESSION_DISCONNECT);
296            try {
297                SomeArgs args = SomeArgs.obtain();
298                args.arg1 = callId;
299                args.arg2 = Log.createSubsession();
300                mHandler.obtainMessage(MSG_DISCONNECT, args).sendToTarget();
301            } finally {
302                Log.endSession();
303            }
304        }
305
306        @Override
307        public void hold(String callId, Session.Info sessionInfo) {
308            Log.startSession(sessionInfo, SESSION_HOLD);
309            try {
310                SomeArgs args = SomeArgs.obtain();
311                args.arg1 = callId;
312                args.arg2 = Log.createSubsession();
313                mHandler.obtainMessage(MSG_HOLD, args).sendToTarget();
314            } finally {
315                Log.endSession();
316            }
317        }
318
319        @Override
320        public void unhold(String callId, Session.Info sessionInfo) {
321            Log.startSession(sessionInfo, SESSION_UNHOLD);
322            try {
323                SomeArgs args = SomeArgs.obtain();
324                args.arg1 = callId;
325                args.arg2 = Log.createSubsession();
326                mHandler.obtainMessage(MSG_UNHOLD, args).sendToTarget();
327            } finally {
328                Log.endSession();
329            }
330        }
331
332        @Override
333        public void onCallAudioStateChanged(String callId, CallAudioState callAudioState,
334                Session.Info sessionInfo) {
335            Log.startSession(sessionInfo, SESSION_CALL_AUDIO_SC);
336            try {
337                SomeArgs args = SomeArgs.obtain();
338                args.arg1 = callId;
339                args.arg2 = callAudioState;
340                args.arg3 = Log.createSubsession();
341                mHandler.obtainMessage(MSG_ON_CALL_AUDIO_STATE_CHANGED, args).sendToTarget();
342            } finally {
343                Log.endSession();
344            }
345        }
346
347        @Override
348        public void playDtmfTone(String callId, char digit, Session.Info sessionInfo) {
349            Log.startSession(sessionInfo, SESSION_PLAY_DTMF);
350            try {
351                SomeArgs args = SomeArgs.obtain();
352                args.arg1 = digit;
353                args.arg2 = callId;
354                args.arg3 = Log.createSubsession();
355                mHandler.obtainMessage(MSG_PLAY_DTMF_TONE, args).sendToTarget();
356            } finally {
357                Log.endSession();
358            }
359        }
360
361        @Override
362        public void stopDtmfTone(String callId, Session.Info sessionInfo) {
363            Log.startSession(sessionInfo, SESSION_STOP_DTMF);
364            try {
365                SomeArgs args = SomeArgs.obtain();
366                args.arg1 = callId;
367                args.arg2 = Log.createSubsession();
368                mHandler.obtainMessage(MSG_STOP_DTMF_TONE, args).sendToTarget();
369            } finally {
370                Log.endSession();
371            }
372        }
373
374        @Override
375        public void conference(String callId1, String callId2, Session.Info sessionInfo) {
376            Log.startSession(sessionInfo, SESSION_CONFERENCE);
377            try {
378                SomeArgs args = SomeArgs.obtain();
379                args.arg1 = callId1;
380                args.arg2 = callId2;
381                args.arg3 = Log.createSubsession();
382                mHandler.obtainMessage(MSG_CONFERENCE, args).sendToTarget();
383            } finally {
384                Log.endSession();
385            }
386        }
387
388        @Override
389        public void splitFromConference(String callId, Session.Info sessionInfo) {
390            Log.startSession(sessionInfo, SESSION_SPLIT_CONFERENCE);
391            try {
392                SomeArgs args = SomeArgs.obtain();
393                args.arg1 = callId;
394                args.arg2 = Log.createSubsession();
395                mHandler.obtainMessage(MSG_SPLIT_FROM_CONFERENCE, args).sendToTarget();
396            } finally {
397                Log.endSession();
398            }
399        }
400
401        @Override
402        public void mergeConference(String callId, Session.Info sessionInfo) {
403            Log.startSession(sessionInfo, SESSION_MERGE_CONFERENCE);
404            try {
405                SomeArgs args = SomeArgs.obtain();
406                args.arg1 = callId;
407                args.arg2 = Log.createSubsession();
408                mHandler.obtainMessage(MSG_MERGE_CONFERENCE, args).sendToTarget();
409            } finally {
410                Log.endSession();
411            }
412        }
413
414        @Override
415        public void swapConference(String callId, Session.Info sessionInfo) {
416            Log.startSession(sessionInfo, SESSION_SWAP_CONFERENCE);
417            try {
418                SomeArgs args = SomeArgs.obtain();
419                args.arg1 = callId;
420                args.arg2 = Log.createSubsession();
421                mHandler.obtainMessage(MSG_SWAP_CONFERENCE, args).sendToTarget();
422            } finally {
423                Log.endSession();
424            }
425        }
426
427        @Override
428        public void onPostDialContinue(String callId, boolean proceed, Session.Info sessionInfo) {
429            Log.startSession(sessionInfo, SESSION_POST_DIAL_CONT);
430            try {
431                SomeArgs args = SomeArgs.obtain();
432                args.arg1 = callId;
433                args.arg2 = Log.createSubsession();
434                args.argi1 = proceed ? 1 : 0;
435                mHandler.obtainMessage(MSG_ON_POST_DIAL_CONTINUE, args).sendToTarget();
436            } finally {
437                Log.endSession();
438            }
439        }
440
441        @Override
442        public void pullExternalCall(String callId, Session.Info sessionInfo) {
443            Log.startSession(sessionInfo, SESSION_PULL_EXTERNAL_CALL);
444            try {
445                SomeArgs args = SomeArgs.obtain();
446                args.arg1 = callId;
447                args.arg2 = Log.createSubsession();
448                mHandler.obtainMessage(MSG_PULL_EXTERNAL_CALL, args).sendToTarget();
449            } finally {
450                Log.endSession();
451            }
452        }
453
454        @Override
455        public void sendCallEvent(String callId, String event, Bundle extras,
456                Session.Info sessionInfo) {
457            Log.startSession(sessionInfo, SESSION_SEND_CALL_EVENT);
458            try {
459                SomeArgs args = SomeArgs.obtain();
460                args.arg1 = callId;
461                args.arg2 = event;
462                args.arg3 = extras;
463                args.arg4 = Log.createSubsession();
464                mHandler.obtainMessage(MSG_SEND_CALL_EVENT, args).sendToTarget();
465            } finally {
466                Log.endSession();
467            }
468        }
469
470        @Override
471        public void onExtrasChanged(String callId, Bundle extras, Session.Info sessionInfo) {
472            Log.startSession(sessionInfo, SESSION_EXTRAS_CHANGED);
473            try {
474                SomeArgs args = SomeArgs.obtain();
475                args.arg1 = callId;
476                args.arg2 = extras;
477                args.arg3 = Log.createSubsession();
478                mHandler.obtainMessage(MSG_ON_EXTRAS_CHANGED, args).sendToTarget();
479            } finally {
480                Log.endSession();
481            }
482        }
483    };
484
485    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
486        @Override
487        public void handleMessage(Message msg) {
488            switch (msg.what) {
489                case MSG_ADD_CONNECTION_SERVICE_ADAPTER: {
490                    SomeArgs args = (SomeArgs) msg.obj;
491                    try {
492                        IConnectionServiceAdapter adapter = (IConnectionServiceAdapter) args.arg1;
493                        Log.continueSession((Session) args.arg2,
494                                SESSION_HANDLER + SESSION_ADD_CS_ADAPTER);
495                        mAdapter.addAdapter(adapter);
496                        onAdapterAttached();
497                    } finally {
498                        args.recycle();
499                        Log.endSession();
500                    }
501                    break;
502                }
503                case MSG_REMOVE_CONNECTION_SERVICE_ADAPTER: {
504                    SomeArgs args = (SomeArgs) msg.obj;
505                    try {
506                        Log.continueSession((Session) args.arg2,
507                                SESSION_HANDLER + SESSION_REMOVE_CS_ADAPTER);
508                        mAdapter.removeAdapter((IConnectionServiceAdapter) args.arg1);
509                    } finally {
510                        args.recycle();
511                        Log.endSession();
512                    }
513                    break;
514                }
515                case MSG_CREATE_CONNECTION: {
516                    SomeArgs args = (SomeArgs) msg.obj;
517                    Log.continueSession((Session) args.arg4, SESSION_HANDLER + SESSION_CREATE_CONN);
518                    try {
519                        final PhoneAccountHandle connectionManagerPhoneAccount =
520                                (PhoneAccountHandle) args.arg1;
521                        final String id = (String) args.arg2;
522                        final ConnectionRequest request = (ConnectionRequest) args.arg3;
523                        final boolean isIncoming = args.argi1 == 1;
524                        final boolean isUnknown = args.argi2 == 1;
525                        if (!mAreAccountsInitialized) {
526                            Log.d(this, "Enqueueing pre-init request %s", id);
527                            mPreInitializationConnectionRequests.add(
528                                    new android.telecom.Logging.Runnable(
529                                            SESSION_HANDLER + SESSION_CREATE_CONN + ".pICR",
530                                            null /*lock*/) {
531                                @Override
532                                public void loggedRun() {
533                                    createConnection(
534                                            connectionManagerPhoneAccount,
535                                            id,
536                                            request,
537                                            isIncoming,
538                                            isUnknown);
539                                }
540                            }.prepare());
541                        } else {
542                            createConnection(
543                                    connectionManagerPhoneAccount,
544                                    id,
545                                    request,
546                                    isIncoming,
547                                    isUnknown);
548                        }
549                    } finally {
550                        args.recycle();
551                        Log.endSession();
552                    }
553                    break;
554                }
555                case MSG_ABORT: {
556                    SomeArgs args = (SomeArgs) msg.obj;
557                    Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_ABORT);
558                    try {
559                        abort((String) args.arg1);
560                    } finally {
561                        args.recycle();
562                        Log.endSession();
563                    }
564                    break;
565                }
566                case MSG_ANSWER: {
567                    SomeArgs args = (SomeArgs) msg.obj;
568                    Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_ANSWER);
569                    try {
570                        answer((String) args.arg1);
571                    } finally {
572                        args.recycle();
573                        Log.endSession();
574                    }
575                    break;
576                }
577                case MSG_ANSWER_VIDEO: {
578                    SomeArgs args = (SomeArgs) msg.obj;
579                    Log.continueSession((Session) args.arg2,
580                            SESSION_HANDLER + SESSION_ANSWER_VIDEO);
581                    try {
582                        String callId = (String) args.arg1;
583                        int videoState = args.argi1;
584                        answerVideo(callId, videoState);
585                    } finally {
586                        args.recycle();
587                        Log.endSession();
588                    }
589                    break;
590                }
591                case MSG_REJECT: {
592                    SomeArgs args = (SomeArgs) msg.obj;
593                    Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_REJECT);
594                    try {
595                        reject((String) args.arg1);
596                    } finally {
597                        args.recycle();
598                        Log.endSession();
599                    }
600                    break;
601                }
602                case MSG_REJECT_WITH_MESSAGE: {
603                    SomeArgs args = (SomeArgs) msg.obj;
604                    Log.continueSession((Session) args.arg3,
605                            SESSION_HANDLER + SESSION_REJECT_MESSAGE);
606                    try {
607                        reject((String) args.arg1, (String) args.arg2);
608                    } finally {
609                        args.recycle();
610                        Log.endSession();
611                    }
612                    break;
613                }
614                case MSG_DISCONNECT: {
615                    SomeArgs args = (SomeArgs) msg.obj;
616                    Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_DISCONNECT);
617                    try {
618                        disconnect((String) args.arg1);
619                    } finally {
620                        args.recycle();
621                        Log.endSession();
622                    }
623                    break;
624                }
625                case MSG_SILENCE: {
626                    SomeArgs args = (SomeArgs) msg.obj;
627                    Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_SILENCE);
628                    try {
629                        silence((String) args.arg1);
630                    } finally {
631                        args.recycle();
632                        Log.endSession();
633                    }
634                    break;
635                }
636                case MSG_HOLD: {
637                    SomeArgs args = (SomeArgs) msg.obj;
638                    Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_REJECT);
639                    try {
640                        hold((String) args.arg1);
641                    } finally {
642                        args.recycle();
643                        Log.endSession();
644                    }
645                    break;
646                }
647                case MSG_UNHOLD: {
648                    SomeArgs args = (SomeArgs) msg.obj;
649                    Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_UNHOLD);
650                    try {
651                        unhold((String) args.arg1);
652                    } finally {
653                        args.recycle();
654                        Log.endSession();
655                    }
656                    break;
657                }
658                case MSG_ON_CALL_AUDIO_STATE_CHANGED: {
659                    SomeArgs args = (SomeArgs) msg.obj;
660                    Log.continueSession((Session) args.arg3,
661                            SESSION_HANDLER + SESSION_CALL_AUDIO_SC);
662                    try {
663                        String callId = (String) args.arg1;
664                        CallAudioState audioState = (CallAudioState) args.arg2;
665                        onCallAudioStateChanged(callId, new CallAudioState(audioState));
666                    } finally {
667                        args.recycle();
668                        Log.endSession();
669                    }
670                    break;
671                }
672                case MSG_PLAY_DTMF_TONE: {
673                    SomeArgs args = (SomeArgs) msg.obj;
674                    try {
675                        Log.continueSession((Session) args.arg3,
676                                SESSION_HANDLER + SESSION_PLAY_DTMF);
677                        playDtmfTone((String) args.arg2, (char) args.arg1);
678                    } finally {
679                        args.recycle();
680                        Log.endSession();
681                    }
682                    break;
683                }
684                case MSG_STOP_DTMF_TONE: {
685                    SomeArgs args = (SomeArgs) msg.obj;
686                    try {
687                        Log.continueSession((Session) args.arg2,
688                                SESSION_HANDLER + SESSION_STOP_DTMF);
689                        stopDtmfTone((String) args.arg1);
690                    } finally {
691                        args.recycle();
692                        Log.endSession();
693                    }
694                    break;
695                }
696                case MSG_CONFERENCE: {
697                    SomeArgs args = (SomeArgs) msg.obj;
698                    try {
699                        Log.continueSession((Session) args.arg3,
700                                SESSION_HANDLER + SESSION_CONFERENCE);
701                        String callId1 = (String) args.arg1;
702                        String callId2 = (String) args.arg2;
703                        conference(callId1, callId2);
704                    } finally {
705                        args.recycle();
706                        Log.endSession();
707                    }
708                    break;
709                }
710                case MSG_SPLIT_FROM_CONFERENCE: {
711                    SomeArgs args = (SomeArgs) msg.obj;
712                    try {
713                        Log.continueSession((Session) args.arg2,
714                                SESSION_HANDLER + SESSION_SPLIT_CONFERENCE);
715                        splitFromConference((String) args.arg1);
716                    } finally {
717                        args.recycle();
718                        Log.endSession();
719                    }
720                    break;
721                }
722                case MSG_MERGE_CONFERENCE: {
723                    SomeArgs args = (SomeArgs) msg.obj;
724                    try {
725                        Log.continueSession((Session) args.arg2,
726                                SESSION_HANDLER + SESSION_MERGE_CONFERENCE);
727                        mergeConference((String) args.arg1);
728                    } finally {
729                        args.recycle();
730                        Log.endSession();
731                    }
732                    break;
733                }
734                case MSG_SWAP_CONFERENCE: {
735                    SomeArgs args = (SomeArgs) msg.obj;
736                    try {
737                        Log.continueSession((Session) args.arg2,
738                                SESSION_HANDLER + SESSION_SWAP_CONFERENCE);
739                        swapConference((String) args.arg1);
740                    } finally {
741                        args.recycle();
742                        Log.endSession();
743                    }
744                    break;
745                }
746                case MSG_ON_POST_DIAL_CONTINUE: {
747                    SomeArgs args = (SomeArgs) msg.obj;
748                    try {
749                        Log.continueSession((Session) args.arg2,
750                                SESSION_HANDLER + SESSION_POST_DIAL_CONT);
751                        String callId = (String) args.arg1;
752                        boolean proceed = (args.argi1 == 1);
753                        onPostDialContinue(callId, proceed);
754                    } finally {
755                        args.recycle();
756                        Log.endSession();
757                    }
758                    break;
759                }
760                case MSG_PULL_EXTERNAL_CALL: {
761                    SomeArgs args = (SomeArgs) msg.obj;
762                    try {
763                        Log.continueSession((Session) args.arg2,
764                                SESSION_HANDLER + SESSION_PULL_EXTERNAL_CALL);
765                        pullExternalCall((String) args.arg1);
766                    } finally {
767                        args.recycle();
768                        Log.endSession();
769                    }
770                    break;
771                }
772                case MSG_SEND_CALL_EVENT: {
773                    SomeArgs args = (SomeArgs) msg.obj;
774                    try {
775                        Log.continueSession((Session) args.arg4,
776                                SESSION_HANDLER + SESSION_SEND_CALL_EVENT);
777                        String callId = (String) args.arg1;
778                        String event = (String) args.arg2;
779                        Bundle extras = (Bundle) args.arg3;
780                        sendCallEvent(callId, event, extras);
781                    } finally {
782                        args.recycle();
783                        Log.endSession();
784                    }
785                    break;
786                }
787                case MSG_ON_EXTRAS_CHANGED: {
788                    SomeArgs args = (SomeArgs) msg.obj;
789                    try {
790                        Log.continueSession((Session) args.arg3,
791                                SESSION_HANDLER + SESSION_EXTRAS_CHANGED);
792                        String callId = (String) args.arg1;
793                        Bundle extras = (Bundle) args.arg2;
794                        handleExtrasChanged(callId, extras);
795                    } finally {
796                        args.recycle();
797                        Log.endSession();
798                    }
799                    break;
800                }
801                default:
802                    break;
803            }
804        }
805    };
806
807    private final Conference.Listener mConferenceListener = new Conference.Listener() {
808        @Override
809        public void onStateChanged(Conference conference, int oldState, int newState) {
810            String id = mIdByConference.get(conference);
811            switch (newState) {
812                case Connection.STATE_ACTIVE:
813                    mAdapter.setActive(id);
814                    break;
815                case Connection.STATE_HOLDING:
816                    mAdapter.setOnHold(id);
817                    break;
818                case Connection.STATE_DISCONNECTED:
819                    // handled by onDisconnected
820                    break;
821            }
822        }
823
824        @Override
825        public void onDisconnected(Conference conference, DisconnectCause disconnectCause) {
826            String id = mIdByConference.get(conference);
827            mAdapter.setDisconnected(id, disconnectCause);
828        }
829
830        @Override
831        public void onConnectionAdded(Conference conference, Connection connection) {
832        }
833
834        @Override
835        public void onConnectionRemoved(Conference conference, Connection connection) {
836        }
837
838        @Override
839        public void onConferenceableConnectionsChanged(
840                Conference conference, List<Connection> conferenceableConnections) {
841            mAdapter.setConferenceableConnections(
842                    mIdByConference.get(conference),
843                    createConnectionIdList(conferenceableConnections));
844        }
845
846        @Override
847        public void onDestroyed(Conference conference) {
848            removeConference(conference);
849        }
850
851        @Override
852        public void onConnectionCapabilitiesChanged(
853                Conference conference,
854                int connectionCapabilities) {
855            String id = mIdByConference.get(conference);
856            Log.d(this, "call capabilities: conference: %s",
857                    Connection.capabilitiesToString(connectionCapabilities));
858            mAdapter.setConnectionCapabilities(id, connectionCapabilities);
859        }
860
861        @Override
862        public void onConnectionPropertiesChanged(
863                Conference conference,
864                int connectionProperties) {
865            String id = mIdByConference.get(conference);
866            Log.d(this, "call capabilities: conference: %s",
867                    Connection.propertiesToString(connectionProperties));
868            mAdapter.setConnectionProperties(id, connectionProperties);
869        }
870
871        @Override
872        public void onVideoStateChanged(Conference c, int videoState) {
873            String id = mIdByConference.get(c);
874            Log.d(this, "onVideoStateChanged set video state %d", videoState);
875            mAdapter.setVideoState(id, videoState);
876        }
877
878        @Override
879        public void onVideoProviderChanged(Conference c, Connection.VideoProvider videoProvider) {
880            String id = mIdByConference.get(c);
881            Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c,
882                    videoProvider);
883            mAdapter.setVideoProvider(id, videoProvider);
884        }
885
886        @Override
887        public void onStatusHintsChanged(Conference conference, StatusHints statusHints) {
888            String id = mIdByConference.get(conference);
889            if (id != null) {
890                mAdapter.setStatusHints(id, statusHints);
891            }
892        }
893
894        @Override
895        public void onExtrasChanged(Conference c, Bundle extras) {
896            String id = mIdByConference.get(c);
897            if (id != null) {
898                mAdapter.putExtras(id, extras);
899            }
900        }
901
902        @Override
903        public void onExtrasRemoved(Conference c, List<String> keys) {
904            String id = mIdByConference.get(c);
905            if (id != null) {
906                mAdapter.removeExtras(id, keys);
907            }
908        }
909    };
910
911    private final Connection.Listener mConnectionListener = new Connection.Listener() {
912        @Override
913        public void onStateChanged(Connection c, int state) {
914            String id = mIdByConnection.get(c);
915            Log.d(this, "Adapter set state %s %s", id, Connection.stateToString(state));
916            switch (state) {
917                case Connection.STATE_ACTIVE:
918                    mAdapter.setActive(id);
919                    break;
920                case Connection.STATE_DIALING:
921                    mAdapter.setDialing(id);
922                    break;
923                case Connection.STATE_PULLING_CALL:
924                    mAdapter.setPulling(id);
925                    break;
926                case Connection.STATE_DISCONNECTED:
927                    // Handled in onDisconnected()
928                    break;
929                case Connection.STATE_HOLDING:
930                    mAdapter.setOnHold(id);
931                    break;
932                case Connection.STATE_NEW:
933                    // Nothing to tell Telecom
934                    break;
935                case Connection.STATE_RINGING:
936                    mAdapter.setRinging(id);
937                    break;
938            }
939        }
940
941        @Override
942        public void onDisconnected(Connection c, DisconnectCause disconnectCause) {
943            String id = mIdByConnection.get(c);
944            Log.d(this, "Adapter set disconnected %s", disconnectCause);
945            mAdapter.setDisconnected(id, disconnectCause);
946        }
947
948        @Override
949        public void onVideoStateChanged(Connection c, int videoState) {
950            String id = mIdByConnection.get(c);
951            Log.d(this, "Adapter set video state %d", videoState);
952            mAdapter.setVideoState(id, videoState);
953        }
954
955        @Override
956        public void onAddressChanged(Connection c, Uri address, int presentation) {
957            String id = mIdByConnection.get(c);
958            mAdapter.setAddress(id, address, presentation);
959        }
960
961        @Override
962        public void onCallerDisplayNameChanged(
963                Connection c, String callerDisplayName, int presentation) {
964            String id = mIdByConnection.get(c);
965            mAdapter.setCallerDisplayName(id, callerDisplayName, presentation);
966        }
967
968        @Override
969        public void onDestroyed(Connection c) {
970            removeConnection(c);
971        }
972
973        @Override
974        public void onPostDialWait(Connection c, String remaining) {
975            String id = mIdByConnection.get(c);
976            Log.d(this, "Adapter onPostDialWait %s, %s", c, remaining);
977            mAdapter.onPostDialWait(id, remaining);
978        }
979
980        @Override
981        public void onPostDialChar(Connection c, char nextChar) {
982            String id = mIdByConnection.get(c);
983            Log.d(this, "Adapter onPostDialChar %s, %s", c, nextChar);
984            mAdapter.onPostDialChar(id, nextChar);
985        }
986
987        @Override
988        public void onRingbackRequested(Connection c, boolean ringback) {
989            String id = mIdByConnection.get(c);
990            Log.d(this, "Adapter onRingback %b", ringback);
991            mAdapter.setRingbackRequested(id, ringback);
992        }
993
994        @Override
995        public void onConnectionCapabilitiesChanged(Connection c, int capabilities) {
996            String id = mIdByConnection.get(c);
997            Log.d(this, "capabilities: parcelableconnection: %s",
998                    Connection.capabilitiesToString(capabilities));
999            mAdapter.setConnectionCapabilities(id, capabilities);
1000        }
1001
1002        @Override
1003        public void onConnectionPropertiesChanged(Connection c, int properties) {
1004            String id = mIdByConnection.get(c);
1005            Log.d(this, "properties: parcelableconnection: %s",
1006                    Connection.propertiesToString(properties));
1007            mAdapter.setConnectionProperties(id, properties);
1008        }
1009
1010        @Override
1011        public void onVideoProviderChanged(Connection c, Connection.VideoProvider videoProvider) {
1012            String id = mIdByConnection.get(c);
1013            Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c,
1014                    videoProvider);
1015            mAdapter.setVideoProvider(id, videoProvider);
1016        }
1017
1018        @Override
1019        public void onAudioModeIsVoipChanged(Connection c, boolean isVoip) {
1020            String id = mIdByConnection.get(c);
1021            mAdapter.setIsVoipAudioMode(id, isVoip);
1022        }
1023
1024        @Override
1025        public void onStatusHintsChanged(Connection c, StatusHints statusHints) {
1026            String id = mIdByConnection.get(c);
1027            mAdapter.setStatusHints(id, statusHints);
1028        }
1029
1030        @Override
1031        public void onConferenceablesChanged(
1032                Connection connection, List<Conferenceable> conferenceables) {
1033            mAdapter.setConferenceableConnections(
1034                    mIdByConnection.get(connection),
1035                    createIdList(conferenceables));
1036        }
1037
1038        @Override
1039        public void onConferenceChanged(Connection connection, Conference conference) {
1040            String id = mIdByConnection.get(connection);
1041            if (id != null) {
1042                String conferenceId = null;
1043                if (conference != null) {
1044                    conferenceId = mIdByConference.get(conference);
1045                }
1046                mAdapter.setIsConferenced(id, conferenceId);
1047            }
1048        }
1049
1050        @Override
1051        public void onConferenceMergeFailed(Connection connection) {
1052            String id = mIdByConnection.get(connection);
1053            if (id != null) {
1054                mAdapter.onConferenceMergeFailed(id);
1055            }
1056        }
1057
1058        @Override
1059        public void onExtrasChanged(Connection c, Bundle extras) {
1060            String id = mIdByConnection.get(c);
1061            if (id != null) {
1062                mAdapter.putExtras(id, extras);
1063            }
1064        }
1065
1066        @Override
1067        public void onExtrasRemoved(Connection c, List<String> keys) {
1068            String id = mIdByConnection.get(c);
1069            if (id != null) {
1070                mAdapter.removeExtras(id, keys);
1071            }
1072        }
1073
1074        @Override
1075        public void onConnectionEvent(Connection connection, String event, Bundle extras) {
1076            String id = mIdByConnection.get(connection);
1077            if (id != null) {
1078                mAdapter.onConnectionEvent(id, event, extras);
1079            }
1080        }
1081
1082        @Override
1083        public void onAudioRouteChanged(Connection c, int audioRoute) {
1084            String id = mIdByConnection.get(c);
1085            if (id != null) {
1086                mAdapter.setAudioRoute(id, audioRoute);
1087            }
1088        }
1089    };
1090
1091    /** {@inheritDoc} */
1092    @Override
1093    public final IBinder onBind(Intent intent) {
1094        return mBinder;
1095    }
1096
1097    /** {@inheritDoc} */
1098    @Override
1099    public boolean onUnbind(Intent intent) {
1100        endAllConnections();
1101        return super.onUnbind(intent);
1102    }
1103
1104    /**
1105     * This can be used by telecom to either create a new outgoing call or attach to an existing
1106     * incoming call. In either case, telecom will cycle through a set of services and call
1107     * createConnection util a connection service cancels the process or completes it successfully.
1108     */
1109    private void createConnection(
1110            final PhoneAccountHandle callManagerAccount,
1111            final String callId,
1112            final ConnectionRequest request,
1113            boolean isIncoming,
1114            boolean isUnknown) {
1115        Log.d(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " +
1116                        "isIncoming: %b, isUnknown: %b", callManagerAccount, callId, request,
1117                isIncoming,
1118                isUnknown);
1119
1120        Connection connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request)
1121                : isIncoming ? onCreateIncomingConnection(callManagerAccount, request)
1122                : onCreateOutgoingConnection(callManagerAccount, request);
1123        Log.d(this, "createConnection, connection: %s", connection);
1124        if (connection == null) {
1125            connection = Connection.createFailedConnection(
1126                    new DisconnectCause(DisconnectCause.ERROR));
1127        }
1128
1129        connection.setTelecomCallId(callId);
1130        if (connection.getState() != Connection.STATE_DISCONNECTED) {
1131            addConnection(callId, connection);
1132        }
1133
1134        Uri address = connection.getAddress();
1135        String number = address == null ? "null" : address.getSchemeSpecificPart();
1136        Log.v(this, "createConnection, number: %s, state: %s, capabilities: %s, properties: %s",
1137                Connection.toLogSafePhoneNumber(number),
1138                Connection.stateToString(connection.getState()),
1139                Connection.capabilitiesToString(connection.getConnectionCapabilities()),
1140                Connection.propertiesToString(connection.getConnectionProperties()));
1141
1142        Log.d(this, "createConnection, calling handleCreateConnectionSuccessful %s", callId);
1143        mAdapter.handleCreateConnectionComplete(
1144                callId,
1145                request,
1146                new ParcelableConnection(
1147                        request.getAccountHandle(),
1148                        connection.getState(),
1149                        connection.getConnectionCapabilities(),
1150                        connection.getConnectionProperties(),
1151                        connection.getSupportedAudioRoutes(),
1152                        connection.getAddress(),
1153                        connection.getAddressPresentation(),
1154                        connection.getCallerDisplayName(),
1155                        connection.getCallerDisplayNamePresentation(),
1156                        connection.getVideoProvider() == null ?
1157                                null : connection.getVideoProvider().getInterface(),
1158                        connection.getVideoState(),
1159                        connection.isRingbackRequested(),
1160                        connection.getAudioModeIsVoip(),
1161                        connection.getConnectTimeMillis(),
1162                        connection.getStatusHints(),
1163                        connection.getDisconnectCause(),
1164                        createIdList(connection.getConferenceables()),
1165                        connection.getExtras()));
1166
1167        if (isIncoming && request.shouldShowIncomingCallUi() &&
1168                (connection.getConnectionProperties() & Connection.PROPERTY_SELF_MANAGED) ==
1169                        Connection.PROPERTY_SELF_MANAGED) {
1170            // Tell ConnectionService to show its incoming call UX.
1171            connection.onShowIncomingCallUi();
1172        }
1173        if (isUnknown) {
1174            triggerConferenceRecalculate();
1175        }
1176    }
1177
1178    private void abort(String callId) {
1179        Log.d(this, "abort %s", callId);
1180        findConnectionForAction(callId, "abort").onAbort();
1181    }
1182
1183    private void answerVideo(String callId, int videoState) {
1184        Log.d(this, "answerVideo %s", callId);
1185        findConnectionForAction(callId, "answer").onAnswer(videoState);
1186    }
1187
1188    private void answer(String callId) {
1189        Log.d(this, "answer %s", callId);
1190        findConnectionForAction(callId, "answer").onAnswer();
1191    }
1192
1193    private void reject(String callId) {
1194        Log.d(this, "reject %s", callId);
1195        findConnectionForAction(callId, "reject").onReject();
1196    }
1197
1198    private void reject(String callId, String rejectWithMessage) {
1199        Log.d(this, "reject %s with message", callId);
1200        findConnectionForAction(callId, "reject").onReject(rejectWithMessage);
1201    }
1202
1203    private void silence(String callId) {
1204        Log.d(this, "silence %s", callId);
1205        findConnectionForAction(callId, "silence").onSilence();
1206    }
1207
1208    private void disconnect(String callId) {
1209        Log.d(this, "disconnect %s", callId);
1210        if (mConnectionById.containsKey(callId)) {
1211            findConnectionForAction(callId, "disconnect").onDisconnect();
1212        } else {
1213            findConferenceForAction(callId, "disconnect").onDisconnect();
1214        }
1215    }
1216
1217    private void hold(String callId) {
1218        Log.d(this, "hold %s", callId);
1219        if (mConnectionById.containsKey(callId)) {
1220            findConnectionForAction(callId, "hold").onHold();
1221        } else {
1222            findConferenceForAction(callId, "hold").onHold();
1223        }
1224    }
1225
1226    private void unhold(String callId) {
1227        Log.d(this, "unhold %s", callId);
1228        if (mConnectionById.containsKey(callId)) {
1229            findConnectionForAction(callId, "unhold").onUnhold();
1230        } else {
1231            findConferenceForAction(callId, "unhold").onUnhold();
1232        }
1233    }
1234
1235    private void onCallAudioStateChanged(String callId, CallAudioState callAudioState) {
1236        Log.d(this, "onAudioStateChanged %s %s", callId, callAudioState);
1237        if (mConnectionById.containsKey(callId)) {
1238            findConnectionForAction(callId, "onCallAudioStateChanged").setCallAudioState(
1239                    callAudioState);
1240        } else {
1241            findConferenceForAction(callId, "onCallAudioStateChanged").setCallAudioState(
1242                    callAudioState);
1243        }
1244    }
1245
1246    private void playDtmfTone(String callId, char digit) {
1247        Log.d(this, "playDtmfTone %s %c", callId, digit);
1248        if (mConnectionById.containsKey(callId)) {
1249            findConnectionForAction(callId, "playDtmfTone").onPlayDtmfTone(digit);
1250        } else {
1251            findConferenceForAction(callId, "playDtmfTone").onPlayDtmfTone(digit);
1252        }
1253    }
1254
1255    private void stopDtmfTone(String callId) {
1256        Log.d(this, "stopDtmfTone %s", callId);
1257        if (mConnectionById.containsKey(callId)) {
1258            findConnectionForAction(callId, "stopDtmfTone").onStopDtmfTone();
1259        } else {
1260            findConferenceForAction(callId, "stopDtmfTone").onStopDtmfTone();
1261        }
1262    }
1263
1264    private void conference(String callId1, String callId2) {
1265        Log.d(this, "conference %s, %s", callId1, callId2);
1266
1267        // Attempt to get second connection or conference.
1268        Connection connection2 = findConnectionForAction(callId2, "conference");
1269        Conference conference2 = getNullConference();
1270        if (connection2 == getNullConnection()) {
1271            conference2 = findConferenceForAction(callId2, "conference");
1272            if (conference2 == getNullConference()) {
1273                Log.w(this, "Connection2 or Conference2 missing in conference request %s.",
1274                        callId2);
1275                return;
1276            }
1277        }
1278
1279        // Attempt to get first connection or conference and perform merge.
1280        Connection connection1 = findConnectionForAction(callId1, "conference");
1281        if (connection1 == getNullConnection()) {
1282            Conference conference1 = findConferenceForAction(callId1, "addConnection");
1283            if (conference1 == getNullConference()) {
1284                Log.w(this,
1285                        "Connection1 or Conference1 missing in conference request %s.",
1286                        callId1);
1287            } else {
1288                // Call 1 is a conference.
1289                if (connection2 != getNullConnection()) {
1290                    // Call 2 is a connection so merge via call 1 (conference).
1291                    conference1.onMerge(connection2);
1292                } else {
1293                    // Call 2 is ALSO a conference; this should never happen.
1294                    Log.wtf(this, "There can only be one conference and an attempt was made to " +
1295                            "merge two conferences.");
1296                    return;
1297                }
1298            }
1299        } else {
1300            // Call 1 is a connection.
1301            if (conference2 != getNullConference()) {
1302                // Call 2 is a conference, so merge via call 2.
1303                conference2.onMerge(connection1);
1304            } else {
1305                // Call 2 is a connection, so merge together.
1306                onConference(connection1, connection2);
1307            }
1308        }
1309    }
1310
1311    private void splitFromConference(String callId) {
1312        Log.d(this, "splitFromConference(%s)", callId);
1313
1314        Connection connection = findConnectionForAction(callId, "splitFromConference");
1315        if (connection == getNullConnection()) {
1316            Log.w(this, "Connection missing in conference request %s.", callId);
1317            return;
1318        }
1319
1320        Conference conference = connection.getConference();
1321        if (conference != null) {
1322            conference.onSeparate(connection);
1323        }
1324    }
1325
1326    private void mergeConference(String callId) {
1327        Log.d(this, "mergeConference(%s)", callId);
1328        Conference conference = findConferenceForAction(callId, "mergeConference");
1329        if (conference != null) {
1330            conference.onMerge();
1331        }
1332    }
1333
1334    private void swapConference(String callId) {
1335        Log.d(this, "swapConference(%s)", callId);
1336        Conference conference = findConferenceForAction(callId, "swapConference");
1337        if (conference != null) {
1338            conference.onSwap();
1339        }
1340    }
1341
1342    /**
1343     * Notifies a {@link Connection} of a request to pull an external call.
1344     *
1345     * See {@link Call#pullExternalCall()}.
1346     *
1347     * @param callId The ID of the call to pull.
1348     */
1349    private void pullExternalCall(String callId) {
1350        Log.d(this, "pullExternalCall(%s)", callId);
1351        Connection connection = findConnectionForAction(callId, "pullExternalCall");
1352        if (connection != null) {
1353            connection.onPullExternalCall();
1354        }
1355    }
1356
1357    /**
1358     * Notifies a {@link Connection} of a call event.
1359     *
1360     * See {@link Call#sendCallEvent(String, Bundle)}.
1361     *
1362     * @param callId The ID of the call receiving the event.
1363     * @param event The event.
1364     * @param extras Extras associated with the event.
1365     */
1366    private void sendCallEvent(String callId, String event, Bundle extras) {
1367        Log.d(this, "sendCallEvent(%s, %s)", callId, event);
1368        Connection connection = findConnectionForAction(callId, "sendCallEvent");
1369        if (connection != null) {
1370            connection.onCallEvent(event, extras);
1371        }
1372
1373    }
1374
1375    /**
1376     * Notifies a {@link Connection} or {@link Conference} of a change to the extras from Telecom.
1377     * <p>
1378     * These extra changes can originate from Telecom itself, or from an {@link InCallService} via
1379     * the {@link android.telecom.Call#putExtra(String, boolean)},
1380     * {@link android.telecom.Call#putExtra(String, int)},
1381     * {@link android.telecom.Call#putExtra(String, String)},
1382     * {@link Call#removeExtras(List)}.
1383     *
1384     * @param callId The ID of the call receiving the event.
1385     * @param extras The new extras bundle.
1386     */
1387    private void handleExtrasChanged(String callId, Bundle extras) {
1388        Log.d(this, "handleExtrasChanged(%s, %s)", callId, extras);
1389        if (mConnectionById.containsKey(callId)) {
1390            findConnectionForAction(callId, "handleExtrasChanged").handleExtrasChanged(extras);
1391        } else if (mConferenceById.containsKey(callId)) {
1392            findConferenceForAction(callId, "handleExtrasChanged").handleExtrasChanged(extras);
1393        }
1394    }
1395
1396    private void onPostDialContinue(String callId, boolean proceed) {
1397        Log.d(this, "onPostDialContinue(%s)", callId);
1398        findConnectionForAction(callId, "stopDtmfTone").onPostDialContinue(proceed);
1399    }
1400
1401    private void onAdapterAttached() {
1402        if (mAreAccountsInitialized) {
1403            // No need to query again if we already did it.
1404            return;
1405        }
1406
1407        mAdapter.queryRemoteConnectionServices(new RemoteServiceCallback.Stub() {
1408            @Override
1409            public void onResult(
1410                    final List<ComponentName> componentNames,
1411                    final List<IBinder> services) {
1412                mHandler.post(new android.telecom.Logging.Runnable("oAA.qRCS.oR", null /*lock*/) {
1413                    @Override
1414                    public void loggedRun() {
1415                        for (int i = 0; i < componentNames.size() && i < services.size(); i++) {
1416                            mRemoteConnectionManager.addConnectionService(
1417                                    componentNames.get(i),
1418                                    IConnectionService.Stub.asInterface(services.get(i)));
1419                        }
1420                        onAccountsInitialized();
1421                        Log.d(this, "remote connection services found: " + services);
1422                    }
1423                }.prepare());
1424            }
1425
1426            @Override
1427            public void onError() {
1428                mHandler.post(new android.telecom.Logging.Runnable("oAA.qRCS.oE", null /*lock*/) {
1429                    @Override
1430                    public void loggedRun() {
1431                        mAreAccountsInitialized = true;
1432                    }
1433                }.prepare());
1434            }
1435        });
1436    }
1437
1438    /**
1439     * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an
1440     * incoming request. This is used by {@code ConnectionService}s that are registered with
1441     * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} and want to be able to manage
1442     * SIM-based incoming calls.
1443     *
1444     * @param connectionManagerPhoneAccount See description at
1445     *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
1446     * @param request Details about the incoming call.
1447     * @return The {@code Connection} object to satisfy this call, or {@code null} to
1448     *         not handle the call.
1449     */
1450    public final RemoteConnection createRemoteIncomingConnection(
1451            PhoneAccountHandle connectionManagerPhoneAccount,
1452            ConnectionRequest request) {
1453        return mRemoteConnectionManager.createRemoteConnection(
1454                connectionManagerPhoneAccount, request, true);
1455    }
1456
1457    /**
1458     * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an
1459     * outgoing request. This is used by {@code ConnectionService}s that are registered with
1460     * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} and want to be able to use the
1461     * SIM-based {@code ConnectionService} to place its outgoing calls.
1462     *
1463     * @param connectionManagerPhoneAccount See description at
1464     *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
1465     * @param request Details about the outgoing call.
1466     * @return The {@code Connection} object to satisfy this call, or {@code null} to
1467     *         not handle the call.
1468     */
1469    public final RemoteConnection createRemoteOutgoingConnection(
1470            PhoneAccountHandle connectionManagerPhoneAccount,
1471            ConnectionRequest request) {
1472        return mRemoteConnectionManager.createRemoteConnection(
1473                connectionManagerPhoneAccount, request, false);
1474    }
1475
1476    /**
1477     * Indicates to the relevant {@code RemoteConnectionService} that the specified
1478     * {@link RemoteConnection}s should be merged into a conference call.
1479     * <p>
1480     * If the conference request is successful, the method {@link #onRemoteConferenceAdded} will
1481     * be invoked.
1482     *
1483     * @param remoteConnection1 The first of the remote connections to conference.
1484     * @param remoteConnection2 The second of the remote connections to conference.
1485     */
1486    public final void conferenceRemoteConnections(
1487            RemoteConnection remoteConnection1,
1488            RemoteConnection remoteConnection2) {
1489        mRemoteConnectionManager.conferenceRemoteConnections(remoteConnection1, remoteConnection2);
1490    }
1491
1492    /**
1493     * Adds a new conference call. When a conference call is created either as a result of an
1494     * explicit request via {@link #onConference} or otherwise, the connection service should supply
1495     * an instance of {@link Conference} by invoking this method. A conference call provided by this
1496     * method will persist until {@link Conference#destroy} is invoked on the conference instance.
1497     *
1498     * @param conference The new conference object.
1499     */
1500    public final void addConference(Conference conference) {
1501        Log.d(this, "addConference: conference=%s", conference);
1502
1503        String id = addConferenceInternal(conference);
1504        if (id != null) {
1505            List<String> connectionIds = new ArrayList<>(2);
1506            for (Connection connection : conference.getConnections()) {
1507                if (mIdByConnection.containsKey(connection)) {
1508                    connectionIds.add(mIdByConnection.get(connection));
1509                }
1510            }
1511            conference.setTelecomCallId(id);
1512            ParcelableConference parcelableConference = new ParcelableConference(
1513                    conference.getPhoneAccountHandle(),
1514                    conference.getState(),
1515                    conference.getConnectionCapabilities(),
1516                    conference.getConnectionProperties(),
1517                    connectionIds,
1518                    conference.getVideoProvider() == null ?
1519                            null : conference.getVideoProvider().getInterface(),
1520                    conference.getVideoState(),
1521                    conference.getConnectTimeMillis(),
1522                    conference.getStatusHints(),
1523                    conference.getExtras());
1524
1525            mAdapter.addConferenceCall(id, parcelableConference);
1526            mAdapter.setVideoProvider(id, conference.getVideoProvider());
1527            mAdapter.setVideoState(id, conference.getVideoState());
1528
1529            // Go through any child calls and set the parent.
1530            for (Connection connection : conference.getConnections()) {
1531                String connectionId = mIdByConnection.get(connection);
1532                if (connectionId != null) {
1533                    mAdapter.setIsConferenced(connectionId, id);
1534                }
1535            }
1536        }
1537    }
1538
1539    /**
1540     * Adds a connection created by the {@link ConnectionService} and informs telecom of the new
1541     * connection.
1542     *
1543     * @param phoneAccountHandle The phone account handle for the connection.
1544     * @param connection The connection to add.
1545     */
1546    public final void addExistingConnection(PhoneAccountHandle phoneAccountHandle,
1547            Connection connection) {
1548
1549        String id = addExistingConnectionInternal(phoneAccountHandle, connection);
1550        if (id != null) {
1551            List<String> emptyList = new ArrayList<>(0);
1552
1553            ParcelableConnection parcelableConnection = new ParcelableConnection(
1554                    phoneAccountHandle,
1555                    connection.getState(),
1556                    connection.getConnectionCapabilities(),
1557                    connection.getConnectionProperties(),
1558                    connection.getSupportedAudioRoutes(),
1559                    connection.getAddress(),
1560                    connection.getAddressPresentation(),
1561                    connection.getCallerDisplayName(),
1562                    connection.getCallerDisplayNamePresentation(),
1563                    connection.getVideoProvider() == null ?
1564                            null : connection.getVideoProvider().getInterface(),
1565                    connection.getVideoState(),
1566                    connection.isRingbackRequested(),
1567                    connection.getAudioModeIsVoip(),
1568                    connection.getConnectTimeMillis(),
1569                    connection.getStatusHints(),
1570                    connection.getDisconnectCause(),
1571                    emptyList,
1572                    connection.getExtras());
1573            mAdapter.addExistingConnection(id, parcelableConnection);
1574        }
1575    }
1576
1577    /**
1578     * Returns all the active {@code Connection}s for which this {@code ConnectionService}
1579     * has taken responsibility.
1580     *
1581     * @return A collection of {@code Connection}s created by this {@code ConnectionService}.
1582     */
1583    public final Collection<Connection> getAllConnections() {
1584        return mConnectionById.values();
1585    }
1586
1587    /**
1588     * Returns all the active {@code Conference}s for which this {@code ConnectionService}
1589     * has taken responsibility.
1590     *
1591     * @return A collection of {@code Conference}s created by this {@code ConnectionService}.
1592     */
1593    public final Collection<Conference> getAllConferences() {
1594        return mConferenceById.values();
1595    }
1596
1597    /**
1598     * Create a {@code Connection} given an incoming request. This is used to attach to existing
1599     * incoming calls.
1600     *
1601     * @param connectionManagerPhoneAccount See description at
1602     *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
1603     * @param request Details about the incoming call.
1604     * @return The {@code Connection} object to satisfy this call, or {@code null} to
1605     *         not handle the call.
1606     */
1607    public Connection onCreateIncomingConnection(
1608            PhoneAccountHandle connectionManagerPhoneAccount,
1609            ConnectionRequest request) {
1610        return null;
1611    }
1612
1613    /**
1614     * Called by Telecom to inform the {@link ConnectionService} that its request to create a new
1615     * incoming {@link Connection} was denied.
1616     * <p>
1617     * Used when a self-managed {@link ConnectionService} attempts to create a new incoming
1618     * {@link Connection}, but Telecom has determined that the call cannot be allowed at this time.
1619     * The {@link ConnectionService} is responsible for silently rejecting the new incoming
1620     * {@link Connection}.
1621     * <p>
1622     * See {@link TelecomManager#isIncomingCallPermitted(PhoneAccountHandle)} for more information.
1623     *
1624     * @param request The incoming connection request.
1625     */
1626    public void onCreateIncomingConnectionFailed(ConnectionRequest request) {
1627    }
1628
1629    /**
1630     * Called by Telecom to inform the {@link ConnectionService} that its request to create a new
1631     * outgoing {@link Connection} was denied.
1632     * <p>
1633     * Used when a self-managed {@link ConnectionService} attempts to create a new outgoing
1634     * {@link Connection}, but Telecom has determined that the call cannot be placed at this time.
1635     * The {@link ConnectionService} is responisible for informing the user that the
1636     * {@link Connection} cannot be made at this time.
1637     * <p>
1638     * See {@link TelecomManager#isOutgoingCallPermitted(PhoneAccountHandle)} for more information.
1639     *
1640     * @param request The outgoing connection request.
1641     */
1642    public void onCreateOutgoingConnectionFailed(ConnectionRequest request) {
1643    }
1644
1645    /**
1646     * Trigger recalculate functinality for conference calls. This is used when a Telephony
1647     * Connection is part of a conference controller but is not yet added to Connection
1648     * Service and hence cannot be added to the conference call.
1649     *
1650     * @hide
1651     */
1652    public void triggerConferenceRecalculate() {
1653    }
1654
1655    /**
1656     * Create a {@code Connection} given an outgoing request. This is used to initiate new
1657     * outgoing calls.
1658     *
1659     * @param connectionManagerPhoneAccount The connection manager account to use for managing
1660     *         this call.
1661     *         <p>
1662     *         If this parameter is not {@code null}, it means that this {@code ConnectionService}
1663     *         has registered one or more {@code PhoneAccount}s having
1664     *         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}. This parameter will contain
1665     *         one of these {@code PhoneAccount}s, while the {@code request} will contain another
1666     *         (usually but not always distinct) {@code PhoneAccount} to be used for actually
1667     *         making the connection.
1668     *         <p>
1669     *         If this parameter is {@code null}, it means that this {@code ConnectionService} is
1670     *         being asked to make a direct connection. The
1671     *         {@link ConnectionRequest#getAccountHandle()} of parameter {@code request} will be
1672     *         a {@code PhoneAccount} registered by this {@code ConnectionService} to use for
1673     *         making the connection.
1674     * @param request Details about the outgoing call.
1675     * @return The {@code Connection} object to satisfy this call, or the result of an invocation
1676     *         of {@link Connection#createFailedConnection(DisconnectCause)} to not handle the call.
1677     */
1678    public Connection onCreateOutgoingConnection(
1679            PhoneAccountHandle connectionManagerPhoneAccount,
1680            ConnectionRequest request) {
1681        return null;
1682    }
1683
1684    /**
1685     * Create a {@code Connection} for a new unknown call. An unknown call is a call originating
1686     * from the ConnectionService that was neither a user-initiated outgoing call, nor an incoming
1687     * call created using
1688     * {@code TelecomManager#addNewIncomingCall(PhoneAccountHandle, android.os.Bundle)}.
1689     *
1690     * @hide
1691     */
1692    public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount,
1693            ConnectionRequest request) {
1694        return null;
1695    }
1696
1697    /**
1698     * Conference two specified connections. Invoked when the user has made a request to merge the
1699     * specified connections into a conference call. In response, the connection service should
1700     * create an instance of {@link Conference} and pass it into {@link #addConference}.
1701     *
1702     * @param connection1 A connection to merge into a conference call.
1703     * @param connection2 A connection to merge into a conference call.
1704     */
1705    public void onConference(Connection connection1, Connection connection2) {}
1706
1707    /**
1708     * Indicates that a remote conference has been created for existing {@link RemoteConnection}s.
1709     * When this method is invoked, this {@link ConnectionService} should create its own
1710     * representation of the conference call and send it to telecom using {@link #addConference}.
1711     * <p>
1712     * This is only relevant to {@link ConnectionService}s which are registered with
1713     * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}.
1714     *
1715     * @param conference The remote conference call.
1716     */
1717    public void onRemoteConferenceAdded(RemoteConference conference) {}
1718
1719    /**
1720     * Called when an existing connection is added remotely.
1721     * @param connection The existing connection which was added.
1722     */
1723    public void onRemoteExistingConnectionAdded(RemoteConnection connection) {}
1724
1725    /**
1726     * @hide
1727     */
1728    public boolean containsConference(Conference conference) {
1729        return mIdByConference.containsKey(conference);
1730    }
1731
1732    /** {@hide} */
1733    void addRemoteConference(RemoteConference remoteConference) {
1734        onRemoteConferenceAdded(remoteConference);
1735    }
1736
1737    /** {@hide} */
1738    void addRemoteExistingConnection(RemoteConnection remoteConnection) {
1739        onRemoteExistingConnectionAdded(remoteConnection);
1740    }
1741
1742    private void onAccountsInitialized() {
1743        mAreAccountsInitialized = true;
1744        for (Runnable r : mPreInitializationConnectionRequests) {
1745            r.run();
1746        }
1747        mPreInitializationConnectionRequests.clear();
1748    }
1749
1750    /**
1751     * Adds an existing connection to the list of connections, identified by a new call ID unique
1752     * to this connection service.
1753     *
1754     * @param connection The connection.
1755     * @return The ID of the connection (e.g. the call-id).
1756     */
1757    private String addExistingConnectionInternal(PhoneAccountHandle handle, Connection connection) {
1758        String id;
1759        if (handle == null) {
1760            // If no phone account handle was provided, we cannot be sure the call ID is unique,
1761            // so just use a random UUID.
1762            id = UUID.randomUUID().toString();
1763        } else {
1764            // Phone account handle was provided, so use the ConnectionService class name as a
1765            // prefix for a unique incremental call ID.
1766            id = handle.getComponentName().getClassName() + "@" + getNextCallId();
1767        }
1768        addConnection(id, connection);
1769        return id;
1770    }
1771
1772    private void addConnection(String callId, Connection connection) {
1773        connection.setTelecomCallId(callId);
1774        mConnectionById.put(callId, connection);
1775        mIdByConnection.put(connection, callId);
1776        connection.addConnectionListener(mConnectionListener);
1777        connection.setConnectionService(this);
1778    }
1779
1780    /** {@hide} */
1781    protected void removeConnection(Connection connection) {
1782        connection.unsetConnectionService(this);
1783        connection.removeConnectionListener(mConnectionListener);
1784        String id = mIdByConnection.get(connection);
1785        if (id != null) {
1786            mConnectionById.remove(id);
1787            mIdByConnection.remove(connection);
1788            mAdapter.removeCall(id);
1789        }
1790    }
1791
1792    private String addConferenceInternal(Conference conference) {
1793        if (mIdByConference.containsKey(conference)) {
1794            Log.w(this, "Re-adding an existing conference: %s.", conference);
1795        } else if (conference != null) {
1796            // Conferences do not (yet) have a PhoneAccountHandle associated with them, so we
1797            // cannot determine a ConnectionService class name to associate with the ID, so use
1798            // a unique UUID (for now).
1799            String id = UUID.randomUUID().toString();
1800            mConferenceById.put(id, conference);
1801            mIdByConference.put(conference, id);
1802            conference.addListener(mConferenceListener);
1803            return id;
1804        }
1805
1806        return null;
1807    }
1808
1809    private void removeConference(Conference conference) {
1810        if (mIdByConference.containsKey(conference)) {
1811            conference.removeListener(mConferenceListener);
1812
1813            String id = mIdByConference.get(conference);
1814            mConferenceById.remove(id);
1815            mIdByConference.remove(conference);
1816            mAdapter.removeCall(id);
1817        }
1818    }
1819
1820    private Connection findConnectionForAction(String callId, String action) {
1821        if (mConnectionById.containsKey(callId)) {
1822            return mConnectionById.get(callId);
1823        }
1824        Log.w(this, "%s - Cannot find Connection %s", action, callId);
1825        return getNullConnection();
1826    }
1827
1828    static synchronized Connection getNullConnection() {
1829        if (sNullConnection == null) {
1830            sNullConnection = new Connection() {};
1831        }
1832        return sNullConnection;
1833    }
1834
1835    private Conference findConferenceForAction(String conferenceId, String action) {
1836        if (mConferenceById.containsKey(conferenceId)) {
1837            return mConferenceById.get(conferenceId);
1838        }
1839        Log.w(this, "%s - Cannot find conference %s", action, conferenceId);
1840        return getNullConference();
1841    }
1842
1843    private List<String> createConnectionIdList(List<Connection> connections) {
1844        List<String> ids = new ArrayList<>();
1845        for (Connection c : connections) {
1846            if (mIdByConnection.containsKey(c)) {
1847                ids.add(mIdByConnection.get(c));
1848            }
1849        }
1850        Collections.sort(ids);
1851        return ids;
1852    }
1853
1854    /**
1855     * Builds a list of {@link Connection} and {@link Conference} IDs based on the list of
1856     * {@link Conferenceable}s passed in.
1857     *
1858     * @param conferenceables The {@link Conferenceable} connections and conferences.
1859     * @return List of string conference and call Ids.
1860     */
1861    private List<String> createIdList(List<Conferenceable> conferenceables) {
1862        List<String> ids = new ArrayList<>();
1863        for (Conferenceable c : conferenceables) {
1864            // Only allow Connection and Conference conferenceables.
1865            if (c instanceof Connection) {
1866                Connection connection = (Connection) c;
1867                if (mIdByConnection.containsKey(connection)) {
1868                    ids.add(mIdByConnection.get(connection));
1869                }
1870            } else if (c instanceof Conference) {
1871                Conference conference = (Conference) c;
1872                if (mIdByConference.containsKey(conference)) {
1873                    ids.add(mIdByConference.get(conference));
1874                }
1875            }
1876        }
1877        Collections.sort(ids);
1878        return ids;
1879    }
1880
1881    private Conference getNullConference() {
1882        if (sNullConference == null) {
1883            sNullConference = new Conference(null) {};
1884        }
1885        return sNullConference;
1886    }
1887
1888    private void endAllConnections() {
1889        // Unbound from telecomm.  We should end all connections and conferences.
1890        for (Connection connection : mIdByConnection.keySet()) {
1891            // only operate on top-level calls. Conference calls will be removed on their own.
1892            if (connection.getConference() == null) {
1893                connection.onDisconnect();
1894            }
1895        }
1896        for (Conference conference : mIdByConference.keySet()) {
1897            conference.onDisconnect();
1898        }
1899    }
1900
1901    /**
1902     * Retrieves the next call ID as maintainted by the connection service.
1903     *
1904     * @return The call ID.
1905     */
1906    private int getNextCallId() {
1907        synchronized (mIdSyncRoot) {
1908            return ++mId;
1909        }
1910    }
1911}
1912