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