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