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 
17 package android.telecom;
18 
19 import android.annotation.SdkConstant;
20 import android.app.Service;
21 import android.content.ComponentName;
22 import android.content.Intent;
23 import android.net.Uri;
24 import android.os.Bundle;
25 import android.os.Handler;
26 import android.os.IBinder;
27 import android.os.Looper;
28 import android.os.Message;
29 import android.os.ParcelFileDescriptor;
30 import android.os.RemoteException;
31 import android.telecom.Logging.Session;
32 
33 import com.android.internal.os.SomeArgs;
34 import com.android.internal.telecom.IConnectionService;
35 import com.android.internal.telecom.IConnectionServiceAdapter;
36 import com.android.internal.telecom.RemoteServiceCallback;
37 
38 import java.util.ArrayList;
39 import java.util.Collection;
40 import java.util.Collections;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.UUID;
44 import 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  */
88 public 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
onBind(Intent intent)1481     public final IBinder onBind(Intent intent) {
1482         return mBinder;
1483     }
1484 
1485     /** {@inheritDoc} */
1486     @Override
onUnbind(Intent intent)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      */
createConnection( final PhoneAccountHandle callManagerAccount, final String callId, final ConnectionRequest request, boolean isIncoming, boolean isUnknown)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 
createConnectionFailed(final PhoneAccountHandle callManagerAccount, final String callId, final ConnectionRequest request, boolean isIncoming)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 
handoverFailed(final String callId, final ConnectionRequest request, int reason)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      */
notifyCreateConnectionComplete(final String callId)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 
abort(String callId)1620     private void abort(String callId) {
1621         Log.d(this, "abort %s", callId);
1622         findConnectionForAction(callId, "abort").onAbort();
1623     }
1624 
answerVideo(String callId, int videoState)1625     private void answerVideo(String callId, int videoState) {
1626         Log.d(this, "answerVideo %s", callId);
1627         findConnectionForAction(callId, "answer").onAnswer(videoState);
1628     }
1629 
answer(String callId)1630     private void answer(String callId) {
1631         Log.d(this, "answer %s", callId);
1632         findConnectionForAction(callId, "answer").onAnswer();
1633     }
1634 
deflect(String callId, Uri address)1635     private void deflect(String callId, Uri address) {
1636         Log.d(this, "deflect %s", callId);
1637         findConnectionForAction(callId, "deflect").onDeflect(address);
1638     }
1639 
reject(String callId)1640     private void reject(String callId) {
1641         Log.d(this, "reject %s", callId);
1642         findConnectionForAction(callId, "reject").onReject();
1643     }
1644 
reject(String callId, String rejectWithMessage)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 
silence(String callId)1650     private void silence(String callId) {
1651         Log.d(this, "silence %s", callId);
1652         findConnectionForAction(callId, "silence").onSilence();
1653     }
1654 
disconnect(String callId)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 
hold(String callId)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 
unhold(String callId)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 
onCallAudioStateChanged(String callId, CallAudioState callAudioState)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 
playDtmfTone(String callId, char digit)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 
stopDtmfTone(String callId)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 
conference(String callId1, String callId2)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 
splitFromConference(String callId)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 
mergeConference(String callId)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 
swapConference(String callId)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      */
pullExternalCall(String callId)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      */
sendCallEvent(String callId, String event, Bundle extras)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      */
notifyHandoverComplete(String callId)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      */
handleExtrasChanged(String callId, Bundle extras)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 
startRtt(String callId, Connection.RttTextStream rttTextStream)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 
stopRtt(String callId)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 
handleRttUpgradeResponse(String callId, Connection.RttTextStream rttTextStream)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 
onPostDialContinue(String callId, boolean proceed)1883     private void onPostDialContinue(String callId, boolean proceed) {
1884         Log.d(this, "onPostDialContinue(%s)", callId);
1885         findConnectionForAction(callId, "stopDtmfTone").onPostDialContinue(proceed);
1886     }
1887 
onAdapterAttached()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      */
createRemoteIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)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      */
createRemoteOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)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      */
conferenceRemoteConnections( RemoteConnection remoteConnection1, RemoteConnection remoteConnection2)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      */
addConference(Conference conference)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      */
addExistingConnection(PhoneAccountHandle phoneAccountHandle, Connection connection)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      */
connectionServiceFocusReleased()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      */
addExistingConnection(PhoneAccountHandle phoneAccountHandle, Connection connection, Conference conference)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      */
getAllConnections()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      */
getAllConferences()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      */
onCreateIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)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      */
onCreateConnectionComplete(Connection connection)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      */
onCreateIncomingConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)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      */
onCreateOutgoingConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)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      */
triggerConferenceRecalculate()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      */
onCreateOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)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      */
onCreateOutgoingHandoverConnection(PhoneAccountHandle fromPhoneAccountHandle, ConnectionRequest request)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      */
onCreateIncomingHandoverConnection(PhoneAccountHandle fromPhoneAccountHandle, ConnectionRequest request)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      */
onHandoverFailed(ConnectionRequest request, @Call.Callback.HandoverFailureErrors int error)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      */
onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)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      */
onConference(Connection connection1, Connection connection2)2355     public void onConference(Connection connection1, Connection connection2) {}
2356 
2357     /**
2358      * Called when a connection is added.
2359      * @hide
2360      */
onConnectionAdded(Connection connection)2361     public void onConnectionAdded(Connection connection) {}
2362 
2363     /**
2364      * Called when a connection is removed.
2365      * @hide
2366      */
onConnectionRemoved(Connection connection)2367     public void onConnectionRemoved(Connection connection) {}
2368 
2369     /**
2370      * Called when a conference is added.
2371      * @hide
2372      */
onConferenceAdded(Conference conference)2373     public void onConferenceAdded(Conference conference) {}
2374 
2375     /**
2376      * Called when a conference is removed.
2377      * @hide
2378      */
onConferenceRemoved(Conference conference)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      */
onRemoteConferenceAdded(RemoteConference conference)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      */
onRemoteExistingConnectionAdded(RemoteConnection connection)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      */
onConnectionServiceFocusLost()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      */
onConnectionServiceFocusGained()2411     public void onConnectionServiceFocusGained() {}
2412 
2413     /**
2414      * @hide
2415      */
containsConference(Conference conference)2416     public boolean containsConference(Conference conference) {
2417         return mIdByConference.containsKey(conference);
2418     }
2419 
2420     /** {@hide} */
addRemoteConference(RemoteConference remoteConference)2421     void addRemoteConference(RemoteConference remoteConference) {
2422         onRemoteConferenceAdded(remoteConference);
2423     }
2424 
2425     /** {@hide} */
addRemoteExistingConnection(RemoteConnection remoteConnection)2426     void addRemoteExistingConnection(RemoteConnection remoteConnection) {
2427         onRemoteExistingConnectionAdded(remoteConnection);
2428     }
2429 
onAccountsInitialized()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      */
addExistingConnectionInternal(PhoneAccountHandle handle, Connection connection)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 
addConnection(PhoneAccountHandle handle, String callId, Connection connection)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} */
removeConnection(Connection connection)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 
addConferenceInternal(Conference conference)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 
removeConference(Conference conference)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 
findConnectionForAction(String callId, String action)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 
getNullConnection()2535     static synchronized Connection getNullConnection() {
2536         if (sNullConnection == null) {
2537             sNullConnection = new Connection() {};
2538         }
2539         return sNullConnection;
2540     }
2541 
findConferenceForAction(String conferenceId, String action)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 
createConnectionIdList(List<Connection> connections)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      */
createIdList(List<Conferenceable> conferenceables)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 
getNullConference()2588     private Conference getNullConference() {
2589         if (sNullConference == null) {
2590             sNullConference = new Conference(null) {};
2591         }
2592         return sNullConference;
2593     }
2594 
endAllConnections()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      */
getNextCallId()2613     private int getNextCallId() {
2614         synchronized (mIdSyncRoot) {
2615             return ++mId;
2616         }
2617     }
2618 }
2619