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