ConnectionService.java revision 0c3541be65fa87519a879c053a7cf4b4526be5db
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_DISCONNECTED:
915                    // Handled in onDisconnected()
916                    break;
917                case Connection.STATE_HOLDING:
918                    mAdapter.setOnHold(id);
919                    break;
920                case Connection.STATE_NEW:
921                    // Nothing to tell Telecom
922                    break;
923                case Connection.STATE_RINGING:
924                    mAdapter.setRinging(id);
925                    break;
926            }
927        }
928
929        @Override
930        public void onDisconnected(Connection c, DisconnectCause disconnectCause) {
931            String id = mIdByConnection.get(c);
932            Log.d(this, "Adapter set disconnected %s", disconnectCause);
933            mAdapter.setDisconnected(id, disconnectCause);
934        }
935
936        @Override
937        public void onVideoStateChanged(Connection c, int videoState) {
938            String id = mIdByConnection.get(c);
939            Log.d(this, "Adapter set video state %d", videoState);
940            mAdapter.setVideoState(id, videoState);
941        }
942
943        @Override
944        public void onAddressChanged(Connection c, Uri address, int presentation) {
945            String id = mIdByConnection.get(c);
946            mAdapter.setAddress(id, address, presentation);
947        }
948
949        @Override
950        public void onCallerDisplayNameChanged(
951                Connection c, String callerDisplayName, int presentation) {
952            String id = mIdByConnection.get(c);
953            mAdapter.setCallerDisplayName(id, callerDisplayName, presentation);
954        }
955
956        @Override
957        public void onDestroyed(Connection c) {
958            removeConnection(c);
959        }
960
961        @Override
962        public void onPostDialWait(Connection c, String remaining) {
963            String id = mIdByConnection.get(c);
964            Log.d(this, "Adapter onPostDialWait %s, %s", c, remaining);
965            mAdapter.onPostDialWait(id, remaining);
966        }
967
968        @Override
969        public void onPostDialChar(Connection c, char nextChar) {
970            String id = mIdByConnection.get(c);
971            Log.d(this, "Adapter onPostDialChar %s, %s", c, nextChar);
972            mAdapter.onPostDialChar(id, nextChar);
973        }
974
975        @Override
976        public void onRingbackRequested(Connection c, boolean ringback) {
977            String id = mIdByConnection.get(c);
978            Log.d(this, "Adapter onRingback %b", ringback);
979            mAdapter.setRingbackRequested(id, ringback);
980        }
981
982        @Override
983        public void onConnectionCapabilitiesChanged(Connection c, int capabilities) {
984            String id = mIdByConnection.get(c);
985            Log.d(this, "capabilities: parcelableconnection: %s",
986                    Connection.capabilitiesToString(capabilities));
987            mAdapter.setConnectionCapabilities(id, capabilities);
988        }
989
990        @Override
991        public void onConnectionPropertiesChanged(Connection c, int properties) {
992            String id = mIdByConnection.get(c);
993            Log.d(this, "properties: parcelableconnection: %s",
994                    Connection.propertiesToString(properties));
995            mAdapter.setConnectionProperties(id, properties);
996        }
997
998        @Override
999        public void onVideoProviderChanged(Connection c, Connection.VideoProvider videoProvider) {
1000            String id = mIdByConnection.get(c);
1001            Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c,
1002                    videoProvider);
1003            mAdapter.setVideoProvider(id, videoProvider);
1004        }
1005
1006        @Override
1007        public void onAudioModeIsVoipChanged(Connection c, boolean isVoip) {
1008            String id = mIdByConnection.get(c);
1009            mAdapter.setIsVoipAudioMode(id, isVoip);
1010        }
1011
1012        @Override
1013        public void onStatusHintsChanged(Connection c, StatusHints statusHints) {
1014            String id = mIdByConnection.get(c);
1015            mAdapter.setStatusHints(id, statusHints);
1016        }
1017
1018        @Override
1019        public void onConferenceablesChanged(
1020                Connection connection, List<Conferenceable> conferenceables) {
1021            mAdapter.setConferenceableConnections(
1022                    mIdByConnection.get(connection),
1023                    createIdList(conferenceables));
1024        }
1025
1026        @Override
1027        public void onConferenceChanged(Connection connection, Conference conference) {
1028            String id = mIdByConnection.get(connection);
1029            if (id != null) {
1030                String conferenceId = null;
1031                if (conference != null) {
1032                    conferenceId = mIdByConference.get(conference);
1033                }
1034                mAdapter.setIsConferenced(id, conferenceId);
1035            }
1036        }
1037
1038        @Override
1039        public void onConferenceMergeFailed(Connection connection) {
1040            String id = mIdByConnection.get(connection);
1041            if (id != null) {
1042                mAdapter.onConferenceMergeFailed(id);
1043            }
1044        }
1045
1046        @Override
1047        public void onExtrasChanged(Connection c, Bundle extras) {
1048            String id = mIdByConnection.get(c);
1049            if (id != null) {
1050                mAdapter.putExtras(id, extras);
1051            }
1052        }
1053
1054        public void onExtrasRemoved(Connection c, List<String> keys) {
1055            String id = mIdByConnection.get(c);
1056            if (id != null) {
1057                mAdapter.removeExtras(id, keys);
1058            }
1059        }
1060
1061
1062        @Override
1063        public void onConnectionEvent(Connection connection, String event, Bundle extras) {
1064            String id = mIdByConnection.get(connection);
1065            if (id != null) {
1066                mAdapter.onConnectionEvent(id, event, extras);
1067            }
1068        }
1069    };
1070
1071    /** {@inheritDoc} */
1072    @Override
1073    public final IBinder onBind(Intent intent) {
1074        return mBinder;
1075    }
1076
1077    /** {@inheritDoc} */
1078    @Override
1079    public boolean onUnbind(Intent intent) {
1080        endAllConnections();
1081        return super.onUnbind(intent);
1082    }
1083
1084    /**
1085     * This can be used by telecom to either create a new outgoing call or attach to an existing
1086     * incoming call. In either case, telecom will cycle through a set of services and call
1087     * createConnection util a connection service cancels the process or completes it successfully.
1088     */
1089    private void createConnection(
1090            final PhoneAccountHandle callManagerAccount,
1091            final String callId,
1092            final ConnectionRequest request,
1093            boolean isIncoming,
1094            boolean isUnknown) {
1095        Log.d(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " +
1096                        "isIncoming: %b, isUnknown: %b", callManagerAccount, callId, request,
1097                isIncoming,
1098                isUnknown);
1099
1100        Connection connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request)
1101                : isIncoming ? onCreateIncomingConnection(callManagerAccount, request)
1102                : onCreateOutgoingConnection(callManagerAccount, request);
1103        Log.d(this, "createConnection, connection: %s", connection);
1104        if (connection == null) {
1105            connection = Connection.createFailedConnection(
1106                    new DisconnectCause(DisconnectCause.ERROR));
1107        }
1108
1109        connection.setTelecomCallId(callId);
1110        if (connection.getState() != Connection.STATE_DISCONNECTED) {
1111            addConnection(callId, connection);
1112        }
1113
1114        Uri address = connection.getAddress();
1115        String number = address == null ? "null" : address.getSchemeSpecificPart();
1116        Log.v(this, "createConnection, number: %s, state: %s, capabilities: %s, properties: %s",
1117                Connection.toLogSafePhoneNumber(number),
1118                Connection.stateToString(connection.getState()),
1119                Connection.capabilitiesToString(connection.getConnectionCapabilities()),
1120                Connection.propertiesToString(connection.getConnectionProperties()));
1121
1122        Log.d(this, "createConnection, calling handleCreateConnectionSuccessful %s", callId);
1123        mAdapter.handleCreateConnectionComplete(
1124                callId,
1125                request,
1126                new ParcelableConnection(
1127                        request.getAccountHandle(),
1128                        connection.getState(),
1129                        connection.getConnectionCapabilities(),
1130                        connection.getConnectionProperties(),
1131                        connection.getAddress(),
1132                        connection.getAddressPresentation(),
1133                        connection.getCallerDisplayName(),
1134                        connection.getCallerDisplayNamePresentation(),
1135                        connection.getVideoProvider() == null ?
1136                                null : connection.getVideoProvider().getInterface(),
1137                        connection.getVideoState(),
1138                        connection.isRingbackRequested(),
1139                        connection.getAudioModeIsVoip(),
1140                        connection.getConnectTimeMillis(),
1141                        connection.getStatusHints(),
1142                        connection.getDisconnectCause(),
1143                        createIdList(connection.getConferenceables()),
1144                        connection.getExtras()));
1145        if (isUnknown) {
1146            triggerConferenceRecalculate();
1147        }
1148    }
1149
1150    private void abort(String callId) {
1151        Log.d(this, "abort %s", callId);
1152        findConnectionForAction(callId, "abort").onAbort();
1153    }
1154
1155    private void answerVideo(String callId, int videoState) {
1156        Log.d(this, "answerVideo %s", callId);
1157        findConnectionForAction(callId, "answer").onAnswer(videoState);
1158    }
1159
1160    private void answer(String callId) {
1161        Log.d(this, "answer %s", callId);
1162        findConnectionForAction(callId, "answer").onAnswer();
1163    }
1164
1165    private void reject(String callId) {
1166        Log.d(this, "reject %s", callId);
1167        findConnectionForAction(callId, "reject").onReject();
1168    }
1169
1170    private void reject(String callId, String rejectWithMessage) {
1171        Log.d(this, "reject %s with message", callId);
1172        findConnectionForAction(callId, "reject").onReject(rejectWithMessage);
1173    }
1174
1175    private void silence(String callId) {
1176        Log.d(this, "silence %s", callId);
1177        findConnectionForAction(callId, "silence").onSilence();
1178    }
1179
1180    private void disconnect(String callId) {
1181        Log.d(this, "disconnect %s", callId);
1182        if (mConnectionById.containsKey(callId)) {
1183            findConnectionForAction(callId, "disconnect").onDisconnect();
1184        } else {
1185            findConferenceForAction(callId, "disconnect").onDisconnect();
1186        }
1187    }
1188
1189    private void hold(String callId) {
1190        Log.d(this, "hold %s", callId);
1191        if (mConnectionById.containsKey(callId)) {
1192            findConnectionForAction(callId, "hold").onHold();
1193        } else {
1194            findConferenceForAction(callId, "hold").onHold();
1195        }
1196    }
1197
1198    private void unhold(String callId) {
1199        Log.d(this, "unhold %s", callId);
1200        if (mConnectionById.containsKey(callId)) {
1201            findConnectionForAction(callId, "unhold").onUnhold();
1202        } else {
1203            findConferenceForAction(callId, "unhold").onUnhold();
1204        }
1205    }
1206
1207    private void onCallAudioStateChanged(String callId, CallAudioState callAudioState) {
1208        Log.d(this, "onAudioStateChanged %s %s", callId, callAudioState);
1209        if (mConnectionById.containsKey(callId)) {
1210            findConnectionForAction(callId, "onCallAudioStateChanged").setCallAudioState(
1211                    callAudioState);
1212        } else {
1213            findConferenceForAction(callId, "onCallAudioStateChanged").setCallAudioState(
1214                    callAudioState);
1215        }
1216    }
1217
1218    private void playDtmfTone(String callId, char digit) {
1219        Log.d(this, "playDtmfTone %s %c", callId, digit);
1220        if (mConnectionById.containsKey(callId)) {
1221            findConnectionForAction(callId, "playDtmfTone").onPlayDtmfTone(digit);
1222        } else {
1223            findConferenceForAction(callId, "playDtmfTone").onPlayDtmfTone(digit);
1224        }
1225    }
1226
1227    private void stopDtmfTone(String callId) {
1228        Log.d(this, "stopDtmfTone %s", callId);
1229        if (mConnectionById.containsKey(callId)) {
1230            findConnectionForAction(callId, "stopDtmfTone").onStopDtmfTone();
1231        } else {
1232            findConferenceForAction(callId, "stopDtmfTone").onStopDtmfTone();
1233        }
1234    }
1235
1236    private void conference(String callId1, String callId2) {
1237        Log.d(this, "conference %s, %s", callId1, callId2);
1238
1239        // Attempt to get second connection or conference.
1240        Connection connection2 = findConnectionForAction(callId2, "conference");
1241        Conference conference2 = getNullConference();
1242        if (connection2 == getNullConnection()) {
1243            conference2 = findConferenceForAction(callId2, "conference");
1244            if (conference2 == getNullConference()) {
1245                Log.w(this, "Connection2 or Conference2 missing in conference request %s.",
1246                        callId2);
1247                return;
1248            }
1249        }
1250
1251        // Attempt to get first connection or conference and perform merge.
1252        Connection connection1 = findConnectionForAction(callId1, "conference");
1253        if (connection1 == getNullConnection()) {
1254            Conference conference1 = findConferenceForAction(callId1, "addConnection");
1255            if (conference1 == getNullConference()) {
1256                Log.w(this,
1257                        "Connection1 or Conference1 missing in conference request %s.",
1258                        callId1);
1259            } else {
1260                // Call 1 is a conference.
1261                if (connection2 != getNullConnection()) {
1262                    // Call 2 is a connection so merge via call 1 (conference).
1263                    conference1.onMerge(connection2);
1264                } else {
1265                    // Call 2 is ALSO a conference; this should never happen.
1266                    Log.wtf(this, "There can only be one conference and an attempt was made to " +
1267                            "merge two conferences.");
1268                    return;
1269                }
1270            }
1271        } else {
1272            // Call 1 is a connection.
1273            if (conference2 != getNullConference()) {
1274                // Call 2 is a conference, so merge via call 2.
1275                conference2.onMerge(connection1);
1276            } else {
1277                // Call 2 is a connection, so merge together.
1278                onConference(connection1, connection2);
1279            }
1280        }
1281    }
1282
1283    private void splitFromConference(String callId) {
1284        Log.d(this, "splitFromConference(%s)", callId);
1285
1286        Connection connection = findConnectionForAction(callId, "splitFromConference");
1287        if (connection == getNullConnection()) {
1288            Log.w(this, "Connection missing in conference request %s.", callId);
1289            return;
1290        }
1291
1292        Conference conference = connection.getConference();
1293        if (conference != null) {
1294            conference.onSeparate(connection);
1295        }
1296    }
1297
1298    private void mergeConference(String callId) {
1299        Log.d(this, "mergeConference(%s)", callId);
1300        Conference conference = findConferenceForAction(callId, "mergeConference");
1301        if (conference != null) {
1302            conference.onMerge();
1303        }
1304    }
1305
1306    private void swapConference(String callId) {
1307        Log.d(this, "swapConference(%s)", callId);
1308        Conference conference = findConferenceForAction(callId, "swapConference");
1309        if (conference != null) {
1310            conference.onSwap();
1311        }
1312    }
1313
1314    /**
1315     * Notifies a {@link Connection} of a request to pull an external call.
1316     *
1317     * See {@link Call#pullExternalCall()}.
1318     *
1319     * @param callId The ID of the call to pull.
1320     */
1321    private void pullExternalCall(String callId) {
1322        Log.d(this, "pullExternalCall(%s)", callId);
1323        Connection connection = findConnectionForAction(callId, "pullExternalCall");
1324        if (connection != null) {
1325            connection.onPullExternalCall();
1326        }
1327    }
1328
1329    /**
1330     * Notifies a {@link Connection} of a call event.
1331     *
1332     * See {@link Call#sendCallEvent(String, Bundle)}.
1333     *
1334     * @param callId The ID of the call receiving the event.
1335     * @param event The event.
1336     * @param extras Extras associated with the event.
1337     */
1338    private void sendCallEvent(String callId, String event, Bundle extras) {
1339        Log.d(this, "sendCallEvent(%s, %s)", callId, event);
1340        Connection connection = findConnectionForAction(callId, "sendCallEvent");
1341        if (connection != null) {
1342            connection.onCallEvent(event, extras);
1343        }
1344
1345    }
1346
1347    /**
1348     * Notifies a {@link Connection} or {@link Conference} of a change to the extras from Telecom.
1349     * <p>
1350     * These extra changes can originate from Telecom itself, or from an {@link InCallService} via
1351     * the {@link android.telecom.Call#putExtra(String, boolean)},
1352     * {@link android.telecom.Call#putExtra(String, int)},
1353     * {@link android.telecom.Call#putExtra(String, String)},
1354     * {@link Call#removeExtras(List)}.
1355     *
1356     * @param callId The ID of the call receiving the event.
1357     * @param extras The new extras bundle.
1358     */
1359    private void handleExtrasChanged(String callId, Bundle extras) {
1360        Log.d(this, "handleExtrasChanged(%s, %s)", callId, extras);
1361        if (mConnectionById.containsKey(callId)) {
1362            findConnectionForAction(callId, "handleExtrasChanged").handleExtrasChanged(extras);
1363        } else if (mConferenceById.containsKey(callId)) {
1364            findConferenceForAction(callId, "handleExtrasChanged").handleExtrasChanged(extras);
1365        }
1366    }
1367
1368    private void onPostDialContinue(String callId, boolean proceed) {
1369        Log.d(this, "onPostDialContinue(%s)", callId);
1370        findConnectionForAction(callId, "stopDtmfTone").onPostDialContinue(proceed);
1371    }
1372
1373    private void onAdapterAttached() {
1374        if (mAreAccountsInitialized) {
1375            // No need to query again if we already did it.
1376            return;
1377        }
1378
1379        mAdapter.queryRemoteConnectionServices(new RemoteServiceCallback.Stub() {
1380            @Override
1381            public void onResult(
1382                    final List<ComponentName> componentNames,
1383                    final List<IBinder> services) {
1384                mHandler.post(new android.telecom.Logging.Runnable("oAA.qRCS.oR", null /*lock*/) {
1385                    @Override
1386                    public void loggedRun() {
1387                        for (int i = 0; i < componentNames.size() && i < services.size(); i++) {
1388                            mRemoteConnectionManager.addConnectionService(
1389                                    componentNames.get(i),
1390                                    IConnectionService.Stub.asInterface(services.get(i)));
1391                        }
1392                        onAccountsInitialized();
1393                        Log.d(this, "remote connection services found: " + services);
1394                    }
1395                }.prepare());
1396            }
1397
1398            @Override
1399            public void onError() {
1400                mHandler.post(new android.telecom.Logging.Runnable("oAA.qRCS.oE", null /*lock*/) {
1401                    @Override
1402                    public void loggedRun() {
1403                        mAreAccountsInitialized = true;
1404                    }
1405                }.prepare());
1406            }
1407        });
1408    }
1409
1410    /**
1411     * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an
1412     * incoming request. This is used by {@code ConnectionService}s that are registered with
1413     * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} and want to be able to manage
1414     * SIM-based incoming calls.
1415     *
1416     * @param connectionManagerPhoneAccount See description at
1417     *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
1418     * @param request Details about the incoming call.
1419     * @return The {@code Connection} object to satisfy this call, or {@code null} to
1420     *         not handle the call.
1421     */
1422    public final RemoteConnection createRemoteIncomingConnection(
1423            PhoneAccountHandle connectionManagerPhoneAccount,
1424            ConnectionRequest request) {
1425        return mRemoteConnectionManager.createRemoteConnection(
1426                connectionManagerPhoneAccount, request, true);
1427    }
1428
1429    /**
1430     * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an
1431     * outgoing request. This is used by {@code ConnectionService}s that are registered with
1432     * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} and want to be able to use the
1433     * SIM-based {@code ConnectionService} to place its outgoing calls.
1434     *
1435     * @param connectionManagerPhoneAccount See description at
1436     *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
1437     * @param request Details about the outgoing call.
1438     * @return The {@code Connection} object to satisfy this call, or {@code null} to
1439     *         not handle the call.
1440     */
1441    public final RemoteConnection createRemoteOutgoingConnection(
1442            PhoneAccountHandle connectionManagerPhoneAccount,
1443            ConnectionRequest request) {
1444        return mRemoteConnectionManager.createRemoteConnection(
1445                connectionManagerPhoneAccount, request, false);
1446    }
1447
1448    /**
1449     * Indicates to the relevant {@code RemoteConnectionService} that the specified
1450     * {@link RemoteConnection}s should be merged into a conference call.
1451     * <p>
1452     * If the conference request is successful, the method {@link #onRemoteConferenceAdded} will
1453     * be invoked.
1454     *
1455     * @param remoteConnection1 The first of the remote connections to conference.
1456     * @param remoteConnection2 The second of the remote connections to conference.
1457     */
1458    public final void conferenceRemoteConnections(
1459            RemoteConnection remoteConnection1,
1460            RemoteConnection remoteConnection2) {
1461        mRemoteConnectionManager.conferenceRemoteConnections(remoteConnection1, remoteConnection2);
1462    }
1463
1464    /**
1465     * Adds a new conference call. When a conference call is created either as a result of an
1466     * explicit request via {@link #onConference} or otherwise, the connection service should supply
1467     * an instance of {@link Conference} by invoking this method. A conference call provided by this
1468     * method will persist until {@link Conference#destroy} is invoked on the conference instance.
1469     *
1470     * @param conference The new conference object.
1471     */
1472    public final void addConference(Conference conference) {
1473        Log.d(this, "addConference: conference=%s", conference);
1474
1475        String id = addConferenceInternal(conference);
1476        if (id != null) {
1477            List<String> connectionIds = new ArrayList<>(2);
1478            for (Connection connection : conference.getConnections()) {
1479                if (mIdByConnection.containsKey(connection)) {
1480                    connectionIds.add(mIdByConnection.get(connection));
1481                }
1482            }
1483            conference.setTelecomCallId(id);
1484            ParcelableConference parcelableConference = new ParcelableConference(
1485                    conference.getPhoneAccountHandle(),
1486                    conference.getState(),
1487                    conference.getConnectionCapabilities(),
1488                    conference.getConnectionProperties(),
1489                    connectionIds,
1490                    conference.getVideoProvider() == null ?
1491                            null : conference.getVideoProvider().getInterface(),
1492                    conference.getVideoState(),
1493                    conference.getConnectTimeMillis(),
1494                    conference.getStatusHints(),
1495                    conference.getExtras());
1496
1497            mAdapter.addConferenceCall(id, parcelableConference);
1498            mAdapter.setVideoProvider(id, conference.getVideoProvider());
1499            mAdapter.setVideoState(id, conference.getVideoState());
1500
1501            // Go through any child calls and set the parent.
1502            for (Connection connection : conference.getConnections()) {
1503                String connectionId = mIdByConnection.get(connection);
1504                if (connectionId != null) {
1505                    mAdapter.setIsConferenced(connectionId, id);
1506                }
1507            }
1508        }
1509    }
1510
1511    /**
1512     * Adds a connection created by the {@link ConnectionService} and informs telecom of the new
1513     * connection.
1514     *
1515     * @param phoneAccountHandle The phone account handle for the connection.
1516     * @param connection The connection to add.
1517     */
1518    public final void addExistingConnection(PhoneAccountHandle phoneAccountHandle,
1519            Connection connection) {
1520
1521        String id = addExistingConnectionInternal(phoneAccountHandle, connection);
1522        if (id != null) {
1523            List<String> emptyList = new ArrayList<>(0);
1524
1525            ParcelableConnection parcelableConnection = new ParcelableConnection(
1526                    phoneAccountHandle,
1527                    connection.getState(),
1528                    connection.getConnectionCapabilities(),
1529                    connection.getConnectionProperties(),
1530                    connection.getAddress(),
1531                    connection.getAddressPresentation(),
1532                    connection.getCallerDisplayName(),
1533                    connection.getCallerDisplayNamePresentation(),
1534                    connection.getVideoProvider() == null ?
1535                            null : connection.getVideoProvider().getInterface(),
1536                    connection.getVideoState(),
1537                    connection.isRingbackRequested(),
1538                    connection.getAudioModeIsVoip(),
1539                    connection.getConnectTimeMillis(),
1540                    connection.getStatusHints(),
1541                    connection.getDisconnectCause(),
1542                    emptyList,
1543                    connection.getExtras());
1544            mAdapter.addExistingConnection(id, parcelableConnection);
1545        }
1546    }
1547
1548    /**
1549     * Returns all the active {@code Connection}s for which this {@code ConnectionService}
1550     * has taken responsibility.
1551     *
1552     * @return A collection of {@code Connection}s created by this {@code ConnectionService}.
1553     */
1554    public final Collection<Connection> getAllConnections() {
1555        return mConnectionById.values();
1556    }
1557
1558    /**
1559     * Returns all the active {@code Conference}s for which this {@code ConnectionService}
1560     * has taken responsibility.
1561     *
1562     * @return A collection of {@code Conference}s created by this {@code ConnectionService}.
1563     */
1564    public final Collection<Conference> getAllConferences() {
1565        return mConferenceById.values();
1566    }
1567
1568    /**
1569     * Create a {@code Connection} given an incoming request. This is used to attach to existing
1570     * incoming calls.
1571     *
1572     * @param connectionManagerPhoneAccount See description at
1573     *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
1574     * @param request Details about the incoming call.
1575     * @return The {@code Connection} object to satisfy this call, or {@code null} to
1576     *         not handle the call.
1577     */
1578    public Connection onCreateIncomingConnection(
1579            PhoneAccountHandle connectionManagerPhoneAccount,
1580            ConnectionRequest request) {
1581        return null;
1582    }
1583
1584    /**
1585     * Trigger recalculate functinality for conference calls. This is used when a Telephony
1586     * Connection is part of a conference controller but is not yet added to Connection
1587     * Service and hence cannot be added to the conference call.
1588     *
1589     * @hide
1590     */
1591    public void triggerConferenceRecalculate() {
1592    }
1593
1594    /**
1595     * Create a {@code Connection} given an outgoing request. This is used to initiate new
1596     * outgoing calls.
1597     *
1598     * @param connectionManagerPhoneAccount The connection manager account to use for managing
1599     *         this call.
1600     *         <p>
1601     *         If this parameter is not {@code null}, it means that this {@code ConnectionService}
1602     *         has registered one or more {@code PhoneAccount}s having
1603     *         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}. This parameter will contain
1604     *         one of these {@code PhoneAccount}s, while the {@code request} will contain another
1605     *         (usually but not always distinct) {@code PhoneAccount} to be used for actually
1606     *         making the connection.
1607     *         <p>
1608     *         If this parameter is {@code null}, it means that this {@code ConnectionService} is
1609     *         being asked to make a direct connection. The
1610     *         {@link ConnectionRequest#getAccountHandle()} of parameter {@code request} will be
1611     *         a {@code PhoneAccount} registered by this {@code ConnectionService} to use for
1612     *         making the connection.
1613     * @param request Details about the outgoing call.
1614     * @return The {@code Connection} object to satisfy this call, or the result of an invocation
1615     *         of {@link Connection#createFailedConnection(DisconnectCause)} to not handle the call.
1616     */
1617    public Connection onCreateOutgoingConnection(
1618            PhoneAccountHandle connectionManagerPhoneAccount,
1619            ConnectionRequest request) {
1620        return null;
1621    }
1622
1623    /**
1624     * Create a {@code Connection} for a new unknown call. An unknown call is a call originating
1625     * from the ConnectionService that was neither a user-initiated outgoing call, nor an incoming
1626     * call created using
1627     * {@code TelecomManager#addNewIncomingCall(PhoneAccountHandle, android.os.Bundle)}.
1628     *
1629     * @hide
1630     */
1631    public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount,
1632            ConnectionRequest request) {
1633        return null;
1634    }
1635
1636    /**
1637     * Conference two specified connections. Invoked when the user has made a request to merge the
1638     * specified connections into a conference call. In response, the connection service should
1639     * create an instance of {@link Conference} and pass it into {@link #addConference}.
1640     *
1641     * @param connection1 A connection to merge into a conference call.
1642     * @param connection2 A connection to merge into a conference call.
1643     */
1644    public void onConference(Connection connection1, Connection connection2) {}
1645
1646    /**
1647     * Indicates that a remote conference has been created for existing {@link RemoteConnection}s.
1648     * When this method is invoked, this {@link ConnectionService} should create its own
1649     * representation of the conference call and send it to telecom using {@link #addConference}.
1650     * <p>
1651     * This is only relevant to {@link ConnectionService}s which are registered with
1652     * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}.
1653     *
1654     * @param conference The remote conference call.
1655     */
1656    public void onRemoteConferenceAdded(RemoteConference conference) {}
1657
1658    /**
1659     * Called when an existing connection is added remotely.
1660     * @param connection The existing connection which was added.
1661     */
1662    public void onRemoteExistingConnectionAdded(RemoteConnection connection) {}
1663
1664    /**
1665     * @hide
1666     */
1667    public boolean containsConference(Conference conference) {
1668        return mIdByConference.containsKey(conference);
1669    }
1670
1671    /** {@hide} */
1672    void addRemoteConference(RemoteConference remoteConference) {
1673        onRemoteConferenceAdded(remoteConference);
1674    }
1675
1676    /** {@hide} */
1677    void addRemoteExistingConnection(RemoteConnection remoteConnection) {
1678        onRemoteExistingConnectionAdded(remoteConnection);
1679    }
1680
1681    private void onAccountsInitialized() {
1682        mAreAccountsInitialized = true;
1683        for (Runnable r : mPreInitializationConnectionRequests) {
1684            r.run();
1685        }
1686        mPreInitializationConnectionRequests.clear();
1687    }
1688
1689    /**
1690     * Adds an existing connection to the list of connections, identified by a new call ID unique
1691     * to this connection service.
1692     *
1693     * @param connection The connection.
1694     * @return The ID of the connection (e.g. the call-id).
1695     */
1696    private String addExistingConnectionInternal(PhoneAccountHandle handle, Connection connection) {
1697        String id;
1698        if (handle == null) {
1699            // If no phone account handle was provided, we cannot be sure the call ID is unique,
1700            // so just use a random UUID.
1701            id = UUID.randomUUID().toString();
1702        } else {
1703            // Phone account handle was provided, so use the ConnectionService class name as a
1704            // prefix for a unique incremental call ID.
1705            id = handle.getComponentName().getClassName() + "@" + getNextCallId();
1706        }
1707        addConnection(id, connection);
1708        return id;
1709    }
1710
1711    private void addConnection(String callId, Connection connection) {
1712        connection.setTelecomCallId(callId);
1713        mConnectionById.put(callId, connection);
1714        mIdByConnection.put(connection, callId);
1715        connection.addConnectionListener(mConnectionListener);
1716        connection.setConnectionService(this);
1717    }
1718
1719    /** {@hide} */
1720    protected void removeConnection(Connection connection) {
1721        connection.unsetConnectionService(this);
1722        connection.removeConnectionListener(mConnectionListener);
1723        String id = mIdByConnection.get(connection);
1724        if (id != null) {
1725            mConnectionById.remove(id);
1726            mIdByConnection.remove(connection);
1727            mAdapter.removeCall(id);
1728        }
1729    }
1730
1731    private String addConferenceInternal(Conference conference) {
1732        if (mIdByConference.containsKey(conference)) {
1733            Log.w(this, "Re-adding an existing conference: %s.", conference);
1734        } else if (conference != null) {
1735            // Conferences do not (yet) have a PhoneAccountHandle associated with them, so we
1736            // cannot determine a ConnectionService class name to associate with the ID, so use
1737            // a unique UUID (for now).
1738            String id = UUID.randomUUID().toString();
1739            mConferenceById.put(id, conference);
1740            mIdByConference.put(conference, id);
1741            conference.addListener(mConferenceListener);
1742            return id;
1743        }
1744
1745        return null;
1746    }
1747
1748    private void removeConference(Conference conference) {
1749        if (mIdByConference.containsKey(conference)) {
1750            conference.removeListener(mConferenceListener);
1751
1752            String id = mIdByConference.get(conference);
1753            mConferenceById.remove(id);
1754            mIdByConference.remove(conference);
1755            mAdapter.removeCall(id);
1756        }
1757    }
1758
1759    private Connection findConnectionForAction(String callId, String action) {
1760        if (mConnectionById.containsKey(callId)) {
1761            return mConnectionById.get(callId);
1762        }
1763        Log.w(this, "%s - Cannot find Connection %s", action, callId);
1764        return getNullConnection();
1765    }
1766
1767    static synchronized Connection getNullConnection() {
1768        if (sNullConnection == null) {
1769            sNullConnection = new Connection() {};
1770        }
1771        return sNullConnection;
1772    }
1773
1774    private Conference findConferenceForAction(String conferenceId, String action) {
1775        if (mConferenceById.containsKey(conferenceId)) {
1776            return mConferenceById.get(conferenceId);
1777        }
1778        Log.w(this, "%s - Cannot find conference %s", action, conferenceId);
1779        return getNullConference();
1780    }
1781
1782    private List<String> createConnectionIdList(List<Connection> connections) {
1783        List<String> ids = new ArrayList<>();
1784        for (Connection c : connections) {
1785            if (mIdByConnection.containsKey(c)) {
1786                ids.add(mIdByConnection.get(c));
1787            }
1788        }
1789        Collections.sort(ids);
1790        return ids;
1791    }
1792
1793    /**
1794     * Builds a list of {@link Connection} and {@link Conference} IDs based on the list of
1795     * {@link Conferenceable}s passed in.
1796     *
1797     * @param conferenceables The {@link Conferenceable} connections and conferences.
1798     * @return List of string conference and call Ids.
1799     */
1800    private List<String> createIdList(List<Conferenceable> conferenceables) {
1801        List<String> ids = new ArrayList<>();
1802        for (Conferenceable c : conferenceables) {
1803            // Only allow Connection and Conference conferenceables.
1804            if (c instanceof Connection) {
1805                Connection connection = (Connection) c;
1806                if (mIdByConnection.containsKey(connection)) {
1807                    ids.add(mIdByConnection.get(connection));
1808                }
1809            } else if (c instanceof Conference) {
1810                Conference conference = (Conference) c;
1811                if (mIdByConference.containsKey(conference)) {
1812                    ids.add(mIdByConference.get(conference));
1813                }
1814            }
1815        }
1816        Collections.sort(ids);
1817        return ids;
1818    }
1819
1820    private Conference getNullConference() {
1821        if (sNullConference == null) {
1822            sNullConference = new Conference(null) {};
1823        }
1824        return sNullConference;
1825    }
1826
1827    private void endAllConnections() {
1828        // Unbound from telecomm.  We should end all connections and conferences.
1829        for (Connection connection : mIdByConnection.keySet()) {
1830            // only operate on top-level calls. Conference calls will be removed on their own.
1831            if (connection.getConference() == null) {
1832                connection.onDisconnect();
1833            }
1834        }
1835        for (Conference conference : mIdByConference.keySet()) {
1836            conference.onDisconnect();
1837        }
1838    }
1839
1840    /**
1841     * Retrieves the next call ID as maintainted by the connection service.
1842     *
1843     * @return The call ID.
1844     */
1845    private int getNextCallId() {
1846        synchronized (mIdSyncRoot) {
1847            return ++mId;
1848        }
1849    }
1850}
1851