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-managed {@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         @Override
1259         public void onConferenceStateChanged(Conference c, boolean isConference) {
1260             String id = mIdByConference.get(c);
1261             if (id != null) {
1262                 mAdapter.setConferenceState(id, isConference);
1263             }
1264         }
1265 
1266         @Override
1267         public void onAddressChanged(Conference c, Uri newAddress, int presentation) {
1268             String id = mIdByConference.get(c);
1269             if (id != null) {
1270                 mAdapter.setAddress(id, newAddress, presentation);
1271             }
1272         }
1273 
1274         @Override
1275         public void onCallerDisplayNameChanged(Conference c, String callerDisplayName,
1276                 int presentation) {
1277             String id = mIdByConference.get(c);
1278             if (id != null) {
1279                 mAdapter.setCallerDisplayName(id, callerDisplayName, presentation);
1280             }
1281         }
1282 
1283         @Override
1284         public void onConnectionEvent(Conference c, String event, Bundle extras) {
1285             String id = mIdByConference.get(c);
1286             if (id != null) {
1287                 mAdapter.onConnectionEvent(id, event, extras);
1288             }
1289         }
1290     };
1291 
1292     private final Connection.Listener mConnectionListener = new Connection.Listener() {
1293         @Override
1294         public void onStateChanged(Connection c, int state) {
1295             String id = mIdByConnection.get(c);
1296             Log.d(this, "Adapter set state %s %s", id, Connection.stateToString(state));
1297             switch (state) {
1298                 case Connection.STATE_ACTIVE:
1299                     mAdapter.setActive(id);
1300                     break;
1301                 case Connection.STATE_DIALING:
1302                     mAdapter.setDialing(id);
1303                     break;
1304                 case Connection.STATE_PULLING_CALL:
1305                     mAdapter.setPulling(id);
1306                     break;
1307                 case Connection.STATE_DISCONNECTED:
1308                     // Handled in onDisconnected()
1309                     break;
1310                 case Connection.STATE_HOLDING:
1311                     mAdapter.setOnHold(id);
1312                     break;
1313                 case Connection.STATE_NEW:
1314                     // Nothing to tell Telecom
1315                     break;
1316                 case Connection.STATE_RINGING:
1317                     mAdapter.setRinging(id);
1318                     break;
1319             }
1320         }
1321 
1322         @Override
1323         public void onDisconnected(Connection c, DisconnectCause disconnectCause) {
1324             String id = mIdByConnection.get(c);
1325             Log.d(this, "Adapter set disconnected %s", disconnectCause);
1326             mAdapter.setDisconnected(id, disconnectCause);
1327         }
1328 
1329         @Override
1330         public void onVideoStateChanged(Connection c, int videoState) {
1331             String id = mIdByConnection.get(c);
1332             Log.d(this, "Adapter set video state %d", videoState);
1333             mAdapter.setVideoState(id, videoState);
1334         }
1335 
1336         @Override
1337         public void onAddressChanged(Connection c, Uri address, int presentation) {
1338             String id = mIdByConnection.get(c);
1339             mAdapter.setAddress(id, address, presentation);
1340         }
1341 
1342         @Override
1343         public void onCallerDisplayNameChanged(
1344                 Connection c, String callerDisplayName, int presentation) {
1345             String id = mIdByConnection.get(c);
1346             mAdapter.setCallerDisplayName(id, callerDisplayName, presentation);
1347         }
1348 
1349         @Override
1350         public void onDestroyed(Connection c) {
1351             removeConnection(c);
1352         }
1353 
1354         @Override
1355         public void onPostDialWait(Connection c, String remaining) {
1356             String id = mIdByConnection.get(c);
1357             Log.d(this, "Adapter onPostDialWait %s, %s", c, remaining);
1358             mAdapter.onPostDialWait(id, remaining);
1359         }
1360 
1361         @Override
1362         public void onPostDialChar(Connection c, char nextChar) {
1363             String id = mIdByConnection.get(c);
1364             Log.d(this, "Adapter onPostDialChar %s, %s", c, nextChar);
1365             mAdapter.onPostDialChar(id, nextChar);
1366         }
1367 
1368         @Override
1369         public void onRingbackRequested(Connection c, boolean ringback) {
1370             String id = mIdByConnection.get(c);
1371             Log.d(this, "Adapter onRingback %b", ringback);
1372             mAdapter.setRingbackRequested(id, ringback);
1373         }
1374 
1375         @Override
1376         public void onConnectionCapabilitiesChanged(Connection c, int capabilities) {
1377             String id = mIdByConnection.get(c);
1378             Log.d(this, "capabilities: parcelableconnection: %s",
1379                     Connection.capabilitiesToString(capabilities));
1380             mAdapter.setConnectionCapabilities(id, capabilities);
1381         }
1382 
1383         @Override
1384         public void onConnectionPropertiesChanged(Connection c, int properties) {
1385             String id = mIdByConnection.get(c);
1386             Log.d(this, "properties: parcelableconnection: %s",
1387                     Connection.propertiesToString(properties));
1388             mAdapter.setConnectionProperties(id, properties);
1389         }
1390 
1391         @Override
1392         public void onVideoProviderChanged(Connection c, Connection.VideoProvider videoProvider) {
1393             String id = mIdByConnection.get(c);
1394             Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c,
1395                     videoProvider);
1396             mAdapter.setVideoProvider(id, videoProvider);
1397         }
1398 
1399         @Override
1400         public void onAudioModeIsVoipChanged(Connection c, boolean isVoip) {
1401             String id = mIdByConnection.get(c);
1402             mAdapter.setIsVoipAudioMode(id, isVoip);
1403         }
1404 
1405         @Override
1406         public void onStatusHintsChanged(Connection c, StatusHints statusHints) {
1407             String id = mIdByConnection.get(c);
1408             mAdapter.setStatusHints(id, statusHints);
1409         }
1410 
1411         @Override
1412         public void onConferenceablesChanged(
1413                 Connection connection, List<Conferenceable> conferenceables) {
1414             mAdapter.setConferenceableConnections(
1415                     mIdByConnection.get(connection),
1416                     createIdList(conferenceables));
1417         }
1418 
1419         @Override
1420         public void onConferenceChanged(Connection connection, Conference conference) {
1421             String id = mIdByConnection.get(connection);
1422             if (id != null) {
1423                 String conferenceId = null;
1424                 if (conference != null) {
1425                     conferenceId = mIdByConference.get(conference);
1426                 }
1427                 mAdapter.setIsConferenced(id, conferenceId);
1428             }
1429         }
1430 
1431         @Override
1432         public void onConferenceMergeFailed(Connection connection) {
1433             String id = mIdByConnection.get(connection);
1434             if (id != null) {
1435                 mAdapter.onConferenceMergeFailed(id);
1436             }
1437         }
1438 
1439         @Override
1440         public void onExtrasChanged(Connection c, Bundle extras) {
1441             String id = mIdByConnection.get(c);
1442             if (id != null) {
1443                 mAdapter.putExtras(id, extras);
1444             }
1445         }
1446 
1447         @Override
1448         public void onExtrasRemoved(Connection c, List<String> keys) {
1449             String id = mIdByConnection.get(c);
1450             if (id != null) {
1451                 mAdapter.removeExtras(id, keys);
1452             }
1453         }
1454 
1455         @Override
1456         public void onConnectionEvent(Connection connection, String event, Bundle extras) {
1457             String id = mIdByConnection.get(connection);
1458             if (id != null) {
1459                 mAdapter.onConnectionEvent(id, event, extras);
1460             }
1461         }
1462 
1463         @Override
1464         public void onAudioRouteChanged(Connection c, int audioRoute, String bluetoothAddress) {
1465             String id = mIdByConnection.get(c);
1466             if (id != null) {
1467                 mAdapter.setAudioRoute(id, audioRoute, bluetoothAddress);
1468             }
1469         }
1470 
1471         @Override
1472         public void onRttInitiationSuccess(Connection c) {
1473             String id = mIdByConnection.get(c);
1474             if (id != null) {
1475                 mAdapter.onRttInitiationSuccess(id);
1476             }
1477         }
1478 
1479         @Override
1480         public void onRttInitiationFailure(Connection c, int reason) {
1481             String id = mIdByConnection.get(c);
1482             if (id != null) {
1483                 mAdapter.onRttInitiationFailure(id, reason);
1484             }
1485         }
1486 
1487         @Override
1488         public void onRttSessionRemotelyTerminated(Connection c) {
1489             String id = mIdByConnection.get(c);
1490             if (id != null) {
1491                 mAdapter.onRttSessionRemotelyTerminated(id);
1492             }
1493         }
1494 
1495         @Override
1496         public void onRemoteRttRequest(Connection c) {
1497             String id = mIdByConnection.get(c);
1498             if (id != null) {
1499                 mAdapter.onRemoteRttRequest(id);
1500             }
1501         }
1502 
1503         @Override
1504         public void onPhoneAccountChanged(Connection c, PhoneAccountHandle pHandle) {
1505             String id = mIdByConnection.get(c);
1506             if (id != null) {
1507                 mAdapter.onPhoneAccountChanged(id, pHandle);
1508             }
1509         }
1510 
1511         public void onConnectionTimeReset(Connection c) {
1512             String id = mIdByConnection.get(c);
1513             if (id != null) {
1514                 mAdapter.resetConnectionTime(id);
1515             }
1516         }
1517     };
1518 
1519     /** {@inheritDoc} */
1520     @Override
onBind(Intent intent)1521     public final IBinder onBind(Intent intent) {
1522         return mBinder;
1523     }
1524 
1525     /** {@inheritDoc} */
1526     @Override
onUnbind(Intent intent)1527     public boolean onUnbind(Intent intent) {
1528         endAllConnections();
1529         return super.onUnbind(intent);
1530     }
1531 
1532     /**
1533      * This can be used by telecom to either create a new outgoing call or attach to an existing
1534      * incoming call. In either case, telecom will cycle through a set of services and call
1535      * createConnection util a connection service cancels the process or completes it successfully.
1536      */
createConnection( final PhoneAccountHandle callManagerAccount, final String callId, final ConnectionRequest request, boolean isIncoming, boolean isUnknown)1537     private void createConnection(
1538             final PhoneAccountHandle callManagerAccount,
1539             final String callId,
1540             final ConnectionRequest request,
1541             boolean isIncoming,
1542             boolean isUnknown) {
1543         boolean isLegacyHandover = request.getExtras() != null &&
1544                 request.getExtras().getBoolean(TelecomManager.EXTRA_IS_HANDOVER, false);
1545         boolean isHandover = request.getExtras() != null && request.getExtras().getBoolean(
1546                 TelecomManager.EXTRA_IS_HANDOVER_CONNECTION, false);
1547         Log.d(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " +
1548                         "isIncoming: %b, isUnknown: %b, isLegacyHandover: %b, isHandover: %b",
1549                 callManagerAccount, callId, request, isIncoming, isUnknown, isLegacyHandover,
1550                 isHandover);
1551 
1552         Connection connection = null;
1553         if (isHandover) {
1554             PhoneAccountHandle fromPhoneAccountHandle = request.getExtras() != null
1555                     ? (PhoneAccountHandle) request.getExtras().getParcelable(
1556                     TelecomManager.EXTRA_HANDOVER_FROM_PHONE_ACCOUNT) : null;
1557             if (!isIncoming) {
1558                 connection = onCreateOutgoingHandoverConnection(fromPhoneAccountHandle, request);
1559             } else {
1560                 connection = onCreateIncomingHandoverConnection(fromPhoneAccountHandle, request);
1561             }
1562         } else {
1563             connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request)
1564                     : isIncoming ? onCreateIncomingConnection(callManagerAccount, request)
1565                     : onCreateOutgoingConnection(callManagerAccount, request);
1566         }
1567         Log.d(this, "createConnection, connection: %s", connection);
1568         if (connection == null) {
1569             Log.i(this, "createConnection, implementation returned null connection.");
1570             connection = Connection.createFailedConnection(
1571                     new DisconnectCause(DisconnectCause.ERROR, "IMPL_RETURNED_NULL_CONNECTION"));
1572         }
1573 
1574         boolean isSelfManaged =
1575                 (connection.getConnectionProperties() & Connection.PROPERTY_SELF_MANAGED)
1576                         == Connection.PROPERTY_SELF_MANAGED;
1577         // Self-managed Connections should always use voip audio mode; we default here so that the
1578         // local state within the ConnectionService matches the default we assume in Telecom.
1579         if (isSelfManaged) {
1580             connection.setAudioModeIsVoip(true);
1581         }
1582         connection.setTelecomCallId(callId);
1583         if (connection.getState() != Connection.STATE_DISCONNECTED) {
1584             addConnection(request.getAccountHandle(), callId, connection);
1585         }
1586 
1587         Uri address = connection.getAddress();
1588         String number = address == null ? "null" : address.getSchemeSpecificPart();
1589         Log.v(this, "createConnection, number: %s, state: %s, capabilities: %s, properties: %s",
1590                 Connection.toLogSafePhoneNumber(number),
1591                 Connection.stateToString(connection.getState()),
1592                 Connection.capabilitiesToString(connection.getConnectionCapabilities()),
1593                 Connection.propertiesToString(connection.getConnectionProperties()));
1594 
1595         Log.d(this, "createConnection, calling handleCreateConnectionSuccessful %s", callId);
1596         mAdapter.handleCreateConnectionComplete(
1597                 callId,
1598                 request,
1599                 new ParcelableConnection(
1600                         request.getAccountHandle(),
1601                         connection.getState(),
1602                         connection.getConnectionCapabilities(),
1603                         connection.getConnectionProperties(),
1604                         connection.getSupportedAudioRoutes(),
1605                         connection.getAddress(),
1606                         connection.getAddressPresentation(),
1607                         connection.getCallerDisplayName(),
1608                         connection.getCallerDisplayNamePresentation(),
1609                         connection.getVideoProvider() == null ?
1610                                 null : connection.getVideoProvider().getInterface(),
1611                         connection.getVideoState(),
1612                         connection.isRingbackRequested(),
1613                         connection.getAudioModeIsVoip(),
1614                         connection.getConnectTimeMillis(),
1615                         connection.getConnectElapsedTimeMillis(),
1616                         connection.getStatusHints(),
1617                         connection.getDisconnectCause(),
1618                         createIdList(connection.getConferenceables()),
1619                         connection.getExtras()));
1620 
1621         if (isIncoming && request.shouldShowIncomingCallUi() && isSelfManaged) {
1622             // Tell ConnectionService to show its incoming call UX.
1623             connection.onShowIncomingCallUi();
1624         }
1625         if (isUnknown) {
1626             triggerConferenceRecalculate();
1627         }
1628     }
1629 
createConnectionFailed(final PhoneAccountHandle callManagerAccount, final String callId, final ConnectionRequest request, boolean isIncoming)1630     private void createConnectionFailed(final PhoneAccountHandle callManagerAccount,
1631                                         final String callId, final ConnectionRequest request,
1632                                         boolean isIncoming) {
1633 
1634         Log.i(this, "createConnectionFailed %s", callId);
1635         if (isIncoming) {
1636             onCreateIncomingConnectionFailed(callManagerAccount, request);
1637         } else {
1638             onCreateOutgoingConnectionFailed(callManagerAccount, request);
1639         }
1640     }
1641 
handoverFailed(final String callId, final ConnectionRequest request, int reason)1642     private void handoverFailed(final String callId, final ConnectionRequest request,
1643                                         int reason) {
1644 
1645         Log.i(this, "handoverFailed %s", callId);
1646         onHandoverFailed(request, reason);
1647     }
1648 
1649     /**
1650      * Called by Telecom when the creation of a new Connection has completed and it is now added
1651      * to Telecom.
1652      * @param callId The ID of the connection.
1653      */
notifyCreateConnectionComplete(final String callId)1654     private void notifyCreateConnectionComplete(final String callId) {
1655         Log.i(this, "notifyCreateConnectionComplete %s", callId);
1656         if (callId == null) {
1657             // This could happen if the connection fails quickly and is removed from the
1658             // ConnectionService before Telecom sends the create connection complete callback.
1659             Log.w(this, "notifyCreateConnectionComplete: callId is null.");
1660             return;
1661         }
1662         onCreateConnectionComplete(findConnectionForAction(callId,
1663                 "notifyCreateConnectionComplete"));
1664     }
1665 
abort(String callId)1666     private void abort(String callId) {
1667         Log.d(this, "abort %s", callId);
1668         findConnectionForAction(callId, "abort").onAbort();
1669     }
1670 
answerVideo(String callId, int videoState)1671     private void answerVideo(String callId, int videoState) {
1672         Log.d(this, "answerVideo %s", callId);
1673         findConnectionForAction(callId, "answer").onAnswer(videoState);
1674     }
1675 
answer(String callId)1676     private void answer(String callId) {
1677         Log.d(this, "answer %s", callId);
1678         findConnectionForAction(callId, "answer").onAnswer();
1679     }
1680 
deflect(String callId, Uri address)1681     private void deflect(String callId, Uri address) {
1682         Log.d(this, "deflect %s", callId);
1683         findConnectionForAction(callId, "deflect").onDeflect(address);
1684     }
1685 
reject(String callId)1686     private void reject(String callId) {
1687         Log.d(this, "reject %s", callId);
1688         findConnectionForAction(callId, "reject").onReject();
1689     }
1690 
reject(String callId, String rejectWithMessage)1691     private void reject(String callId, String rejectWithMessage) {
1692         Log.d(this, "reject %s with message", callId);
1693         findConnectionForAction(callId, "reject").onReject(rejectWithMessage);
1694     }
1695 
silence(String callId)1696     private void silence(String callId) {
1697         Log.d(this, "silence %s", callId);
1698         findConnectionForAction(callId, "silence").onSilence();
1699     }
1700 
disconnect(String callId)1701     private void disconnect(String callId) {
1702         Log.d(this, "disconnect %s", callId);
1703         if (mConnectionById.containsKey(callId)) {
1704             findConnectionForAction(callId, "disconnect").onDisconnect();
1705         } else {
1706             findConferenceForAction(callId, "disconnect").onDisconnect();
1707         }
1708     }
1709 
hold(String callId)1710     private void hold(String callId) {
1711         Log.d(this, "hold %s", callId);
1712         if (mConnectionById.containsKey(callId)) {
1713             findConnectionForAction(callId, "hold").onHold();
1714         } else {
1715             findConferenceForAction(callId, "hold").onHold();
1716         }
1717     }
1718 
unhold(String callId)1719     private void unhold(String callId) {
1720         Log.d(this, "unhold %s", callId);
1721         if (mConnectionById.containsKey(callId)) {
1722             findConnectionForAction(callId, "unhold").onUnhold();
1723         } else {
1724             findConferenceForAction(callId, "unhold").onUnhold();
1725         }
1726     }
1727 
onCallAudioStateChanged(String callId, CallAudioState callAudioState)1728     private void onCallAudioStateChanged(String callId, CallAudioState callAudioState) {
1729         Log.d(this, "onAudioStateChanged %s %s", callId, callAudioState);
1730         if (mConnectionById.containsKey(callId)) {
1731             findConnectionForAction(callId, "onCallAudioStateChanged").setCallAudioState(
1732                     callAudioState);
1733         } else {
1734             findConferenceForAction(callId, "onCallAudioStateChanged").setCallAudioState(
1735                     callAudioState);
1736         }
1737     }
1738 
playDtmfTone(String callId, char digit)1739     private void playDtmfTone(String callId, char digit) {
1740         Log.d(this, "playDtmfTone %s %c", callId, digit);
1741         if (mConnectionById.containsKey(callId)) {
1742             findConnectionForAction(callId, "playDtmfTone").onPlayDtmfTone(digit);
1743         } else {
1744             findConferenceForAction(callId, "playDtmfTone").onPlayDtmfTone(digit);
1745         }
1746     }
1747 
stopDtmfTone(String callId)1748     private void stopDtmfTone(String callId) {
1749         Log.d(this, "stopDtmfTone %s", callId);
1750         if (mConnectionById.containsKey(callId)) {
1751             findConnectionForAction(callId, "stopDtmfTone").onStopDtmfTone();
1752         } else {
1753             findConferenceForAction(callId, "stopDtmfTone").onStopDtmfTone();
1754         }
1755     }
1756 
conference(String callId1, String callId2)1757     private void conference(String callId1, String callId2) {
1758         Log.d(this, "conference %s, %s", callId1, callId2);
1759 
1760         // Attempt to get second connection or conference.
1761         Connection connection2 = findConnectionForAction(callId2, "conference");
1762         Conference conference2 = getNullConference();
1763         if (connection2 == getNullConnection()) {
1764             conference2 = findConferenceForAction(callId2, "conference");
1765             if (conference2 == getNullConference()) {
1766                 Log.w(this, "Connection2 or Conference2 missing in conference request %s.",
1767                         callId2);
1768                 return;
1769             }
1770         }
1771 
1772         // Attempt to get first connection or conference and perform merge.
1773         Connection connection1 = findConnectionForAction(callId1, "conference");
1774         if (connection1 == getNullConnection()) {
1775             Conference conference1 = findConferenceForAction(callId1, "addConnection");
1776             if (conference1 == getNullConference()) {
1777                 Log.w(this,
1778                         "Connection1 or Conference1 missing in conference request %s.",
1779                         callId1);
1780             } else {
1781                 // Call 1 is a conference.
1782                 if (connection2 != getNullConnection()) {
1783                     // Call 2 is a connection so merge via call 1 (conference).
1784                     conference1.onMerge(connection2);
1785                 } else {
1786                     // Call 2 is ALSO a conference; this should never happen.
1787                     Log.wtf(this, "There can only be one conference and an attempt was made to " +
1788                             "merge two conferences.");
1789                     return;
1790                 }
1791             }
1792         } else {
1793             // Call 1 is a connection.
1794             if (conference2 != getNullConference()) {
1795                 // Call 2 is a conference, so merge via call 2.
1796                 conference2.onMerge(connection1);
1797             } else {
1798                 // Call 2 is a connection, so merge together.
1799                 onConference(connection1, connection2);
1800             }
1801         }
1802     }
1803 
splitFromConference(String callId)1804     private void splitFromConference(String callId) {
1805         Log.d(this, "splitFromConference(%s)", callId);
1806 
1807         Connection connection = findConnectionForAction(callId, "splitFromConference");
1808         if (connection == getNullConnection()) {
1809             Log.w(this, "Connection missing in conference request %s.", callId);
1810             return;
1811         }
1812 
1813         Conference conference = connection.getConference();
1814         if (conference != null) {
1815             conference.onSeparate(connection);
1816         }
1817     }
1818 
mergeConference(String callId)1819     private void mergeConference(String callId) {
1820         Log.d(this, "mergeConference(%s)", callId);
1821         Conference conference = findConferenceForAction(callId, "mergeConference");
1822         if (conference != null) {
1823             conference.onMerge();
1824         }
1825     }
1826 
swapConference(String callId)1827     private void swapConference(String callId) {
1828         Log.d(this, "swapConference(%s)", callId);
1829         Conference conference = findConferenceForAction(callId, "swapConference");
1830         if (conference != null) {
1831             conference.onSwap();
1832         }
1833     }
1834 
1835     /**
1836      * Notifies a {@link Connection} of a request to pull an external call.
1837      *
1838      * See {@link Call#pullExternalCall()}.
1839      *
1840      * @param callId The ID of the call to pull.
1841      */
pullExternalCall(String callId)1842     private void pullExternalCall(String callId) {
1843         Log.d(this, "pullExternalCall(%s)", callId);
1844         Connection connection = findConnectionForAction(callId, "pullExternalCall");
1845         if (connection != null) {
1846             connection.onPullExternalCall();
1847         }
1848     }
1849 
1850     /**
1851      * Notifies a {@link Connection} of a call event.
1852      *
1853      * See {@link Call#sendCallEvent(String, Bundle)}.
1854      *
1855      * @param callId The ID of the call receiving the event.
1856      * @param event The event.
1857      * @param extras Extras associated with the event.
1858      */
sendCallEvent(String callId, String event, Bundle extras)1859     private void sendCallEvent(String callId, String event, Bundle extras) {
1860         Log.d(this, "sendCallEvent(%s, %s)", callId, event);
1861         Connection connection = findConnectionForAction(callId, "sendCallEvent");
1862         if (connection != null) {
1863             connection.onCallEvent(event, extras);
1864         }
1865     }
1866 
1867     /**
1868      * Notifies a {@link Connection} that a handover has completed.
1869      *
1870      * @param callId The ID of the call which completed handover.
1871      */
notifyHandoverComplete(String callId)1872     private void notifyHandoverComplete(String callId) {
1873         Log.d(this, "notifyHandoverComplete(%s)", callId);
1874         Connection connection = findConnectionForAction(callId, "notifyHandoverComplete");
1875         if (connection != null) {
1876             connection.onHandoverComplete();
1877         }
1878     }
1879 
1880     /**
1881      * Notifies a {@link Connection} or {@link Conference} of a change to the extras from Telecom.
1882      * <p>
1883      * These extra changes can originate from Telecom itself, or from an {@link InCallService} via
1884      * the {@link android.telecom.Call#putExtra(String, boolean)},
1885      * {@link android.telecom.Call#putExtra(String, int)},
1886      * {@link android.telecom.Call#putExtra(String, String)},
1887      * {@link Call#removeExtras(List)}.
1888      *
1889      * @param callId The ID of the call receiving the event.
1890      * @param extras The new extras bundle.
1891      */
handleExtrasChanged(String callId, Bundle extras)1892     private void handleExtrasChanged(String callId, Bundle extras) {
1893         Log.d(this, "handleExtrasChanged(%s, %s)", callId, extras);
1894         if (mConnectionById.containsKey(callId)) {
1895             findConnectionForAction(callId, "handleExtrasChanged").handleExtrasChanged(extras);
1896         } else if (mConferenceById.containsKey(callId)) {
1897             findConferenceForAction(callId, "handleExtrasChanged").handleExtrasChanged(extras);
1898         }
1899     }
1900 
startRtt(String callId, Connection.RttTextStream rttTextStream)1901     private void startRtt(String callId, Connection.RttTextStream rttTextStream) {
1902         Log.d(this, "startRtt(%s)", callId);
1903         if (mConnectionById.containsKey(callId)) {
1904             findConnectionForAction(callId, "startRtt").onStartRtt(rttTextStream);
1905         } else if (mConferenceById.containsKey(callId)) {
1906             Log.w(this, "startRtt called on a conference.");
1907         }
1908     }
1909 
stopRtt(String callId)1910     private void stopRtt(String callId) {
1911         Log.d(this, "stopRtt(%s)", callId);
1912         if (mConnectionById.containsKey(callId)) {
1913             findConnectionForAction(callId, "stopRtt").onStopRtt();
1914         } else if (mConferenceById.containsKey(callId)) {
1915             Log.w(this, "stopRtt called on a conference.");
1916         }
1917     }
1918 
handleRttUpgradeResponse(String callId, Connection.RttTextStream rttTextStream)1919     private void handleRttUpgradeResponse(String callId, Connection.RttTextStream rttTextStream) {
1920         Log.d(this, "handleRttUpgradeResponse(%s, %s)", callId, rttTextStream == null);
1921         if (mConnectionById.containsKey(callId)) {
1922             findConnectionForAction(callId, "handleRttUpgradeResponse")
1923                     .handleRttUpgradeResponse(rttTextStream);
1924         } else if (mConferenceById.containsKey(callId)) {
1925             Log.w(this, "handleRttUpgradeResponse called on a conference.");
1926         }
1927     }
1928 
onPostDialContinue(String callId, boolean proceed)1929     private void onPostDialContinue(String callId, boolean proceed) {
1930         Log.d(this, "onPostDialContinue(%s)", callId);
1931         findConnectionForAction(callId, "stopDtmfTone").onPostDialContinue(proceed);
1932     }
1933 
onAdapterAttached()1934     private void onAdapterAttached() {
1935         if (mAreAccountsInitialized) {
1936             // No need to query again if we already did it.
1937             return;
1938         }
1939 
1940         String callingPackage = getOpPackageName();
1941 
1942         mAdapter.queryRemoteConnectionServices(new RemoteServiceCallback.Stub() {
1943             @Override
1944             public void onResult(
1945                     final List<ComponentName> componentNames,
1946                     final List<IBinder> services) {
1947                 mHandler.post(new android.telecom.Logging.Runnable("oAA.qRCS.oR", null /*lock*/) {
1948                     @Override
1949                     public void loggedRun() {
1950                         for (int i = 0; i < componentNames.size() && i < services.size(); i++) {
1951                             mRemoteConnectionManager.addConnectionService(
1952                                     componentNames.get(i),
1953                                     IConnectionService.Stub.asInterface(services.get(i)));
1954                         }
1955                         onAccountsInitialized();
1956                         Log.d(this, "remote connection services found: " + services);
1957                     }
1958                 }.prepare());
1959             }
1960 
1961             @Override
1962             public void onError() {
1963                 mHandler.post(new android.telecom.Logging.Runnable("oAA.qRCS.oE", null /*lock*/) {
1964                     @Override
1965                     public void loggedRun() {
1966                         mAreAccountsInitialized = true;
1967                     }
1968                 }.prepare());
1969             }
1970         }, callingPackage);
1971     }
1972 
1973     /**
1974      * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an
1975      * incoming request. This is used by {@code ConnectionService}s that are registered with
1976      * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} and want to be able to manage
1977      * SIM-based incoming calls.
1978      *
1979      * @param connectionManagerPhoneAccount See description at
1980      *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
1981      * @param request Details about the incoming call.
1982      * @return The {@code Connection} object to satisfy this call, or {@code null} to
1983      *         not handle the call.
1984      */
createRemoteIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1985     public final RemoteConnection createRemoteIncomingConnection(
1986             PhoneAccountHandle connectionManagerPhoneAccount,
1987             ConnectionRequest request) {
1988         return mRemoteConnectionManager.createRemoteConnection(
1989                 connectionManagerPhoneAccount, request, true);
1990     }
1991 
1992     /**
1993      * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an
1994      * outgoing request. This is used by {@code ConnectionService}s that are registered with
1995      * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} and want to be able to use the
1996      * SIM-based {@code ConnectionService} to place its outgoing calls.
1997      *
1998      * @param connectionManagerPhoneAccount See description at
1999      *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
2000      * @param request Details about the outgoing call.
2001      * @return The {@code Connection} object to satisfy this call, or {@code null} to
2002      *         not handle the call.
2003      */
createRemoteOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)2004     public final RemoteConnection createRemoteOutgoingConnection(
2005             PhoneAccountHandle connectionManagerPhoneAccount,
2006             ConnectionRequest request) {
2007         return mRemoteConnectionManager.createRemoteConnection(
2008                 connectionManagerPhoneAccount, request, false);
2009     }
2010 
2011     /**
2012      * Indicates to the relevant {@code RemoteConnectionService} that the specified
2013      * {@link RemoteConnection}s should be merged into a conference call.
2014      * <p>
2015      * If the conference request is successful, the method {@link #onRemoteConferenceAdded} will
2016      * be invoked.
2017      *
2018      * @param remoteConnection1 The first of the remote connections to conference.
2019      * @param remoteConnection2 The second of the remote connections to conference.
2020      */
conferenceRemoteConnections( RemoteConnection remoteConnection1, RemoteConnection remoteConnection2)2021     public final void conferenceRemoteConnections(
2022             RemoteConnection remoteConnection1,
2023             RemoteConnection remoteConnection2) {
2024         mRemoteConnectionManager.conferenceRemoteConnections(remoteConnection1, remoteConnection2);
2025     }
2026 
2027     /**
2028      * Adds a new conference call. When a conference call is created either as a result of an
2029      * explicit request via {@link #onConference} or otherwise, the connection service should supply
2030      * an instance of {@link Conference} by invoking this method. A conference call provided by this
2031      * method will persist until {@link Conference#destroy} is invoked on the conference instance.
2032      *
2033      * @param conference The new conference object.
2034      */
addConference(Conference conference)2035     public final void addConference(Conference conference) {
2036         Log.d(this, "addConference: conference=%s", conference);
2037 
2038         String id = addConferenceInternal(conference);
2039         if (id != null) {
2040             List<String> connectionIds = new ArrayList<>(2);
2041             for (Connection connection : conference.getConnections()) {
2042                 if (mIdByConnection.containsKey(connection)) {
2043                     connectionIds.add(mIdByConnection.get(connection));
2044                 }
2045             }
2046             conference.setTelecomCallId(id);
2047             ParcelableConference parcelableConference = new ParcelableConference(
2048                     conference.getPhoneAccountHandle(),
2049                     conference.getState(),
2050                     conference.getConnectionCapabilities(),
2051                     conference.getConnectionProperties(),
2052                     connectionIds,
2053                     conference.getVideoProvider() == null ?
2054                             null : conference.getVideoProvider().getInterface(),
2055                     conference.getVideoState(),
2056                     conference.getConnectTimeMillis(),
2057                     conference.getConnectionStartElapsedRealTime(),
2058                     conference.getStatusHints(),
2059                     conference.getExtras(),
2060                     conference.getAddress(),
2061                     conference.getAddressPresentation(),
2062                     conference.getCallerDisplayName(),
2063                     conference.getCallerDisplayNamePresentation());
2064 
2065             mAdapter.addConferenceCall(id, parcelableConference);
2066             mAdapter.setVideoProvider(id, conference.getVideoProvider());
2067             mAdapter.setVideoState(id, conference.getVideoState());
2068 
2069             // Go through any child calls and set the parent.
2070             for (Connection connection : conference.getConnections()) {
2071                 String connectionId = mIdByConnection.get(connection);
2072                 if (connectionId != null) {
2073                     mAdapter.setIsConferenced(connectionId, id);
2074                 }
2075             }
2076             onConferenceAdded(conference);
2077         }
2078     }
2079 
2080     /**
2081      * Adds a connection created by the {@link ConnectionService} and informs telecom of the new
2082      * connection.
2083      *
2084      * @param phoneAccountHandle The phone account handle for the connection.
2085      * @param connection The connection to add.
2086      */
addExistingConnection(PhoneAccountHandle phoneAccountHandle, Connection connection)2087     public final void addExistingConnection(PhoneAccountHandle phoneAccountHandle,
2088             Connection connection) {
2089         addExistingConnection(phoneAccountHandle, connection, null /* conference */);
2090     }
2091 
2092     /**
2093      * Call to inform Telecom that your {@link ConnectionService} has released call resources (e.g
2094      * microphone, camera).
2095      *
2096      * <p>
2097      * The {@link ConnectionService} will be disconnected when it failed to call this method within
2098      * 5 seconds after {@link #onConnectionServiceFocusLost()} is called.
2099      *
2100      * @see ConnectionService#onConnectionServiceFocusLost()
2101      */
connectionServiceFocusReleased()2102     public final void connectionServiceFocusReleased() {
2103         mAdapter.onConnectionServiceFocusReleased();
2104     }
2105 
2106     /**
2107      * Adds a connection created by the {@link ConnectionService} and informs telecom of the new
2108      * connection.
2109      *
2110      * @param phoneAccountHandle The phone account handle for the connection.
2111      * @param connection The connection to add.
2112      * @param conference The parent conference of the new connection.
2113      * @hide
2114      */
addExistingConnection(PhoneAccountHandle phoneAccountHandle, Connection connection, Conference conference)2115     public final void addExistingConnection(PhoneAccountHandle phoneAccountHandle,
2116             Connection connection, Conference conference) {
2117 
2118         String id = addExistingConnectionInternal(phoneAccountHandle, connection);
2119         if (id != null) {
2120             List<String> emptyList = new ArrayList<>(0);
2121             String conferenceId = null;
2122             if (conference != null) {
2123                 conferenceId = mIdByConference.get(conference);
2124             }
2125 
2126             ParcelableConnection parcelableConnection = new ParcelableConnection(
2127                     phoneAccountHandle,
2128                     connection.getState(),
2129                     connection.getConnectionCapabilities(),
2130                     connection.getConnectionProperties(),
2131                     connection.getSupportedAudioRoutes(),
2132                     connection.getAddress(),
2133                     connection.getAddressPresentation(),
2134                     connection.getCallerDisplayName(),
2135                     connection.getCallerDisplayNamePresentation(),
2136                     connection.getVideoProvider() == null ?
2137                             null : connection.getVideoProvider().getInterface(),
2138                     connection.getVideoState(),
2139                     connection.isRingbackRequested(),
2140                     connection.getAudioModeIsVoip(),
2141                     connection.getConnectTimeMillis(),
2142                     connection.getConnectElapsedTimeMillis(),
2143                     connection.getStatusHints(),
2144                     connection.getDisconnectCause(),
2145                     emptyList,
2146                     connection.getExtras(),
2147                     conferenceId,
2148                     connection.getCallDirection());
2149             mAdapter.addExistingConnection(id, parcelableConnection);
2150         }
2151     }
2152 
2153     /**
2154      * Returns all the active {@code Connection}s for which this {@code ConnectionService}
2155      * has taken responsibility.
2156      *
2157      * @return A collection of {@code Connection}s created by this {@code ConnectionService}.
2158      */
getAllConnections()2159     public final Collection<Connection> getAllConnections() {
2160         return mConnectionById.values();
2161     }
2162 
2163     /**
2164      * Returns all the active {@code Conference}s for which this {@code ConnectionService}
2165      * has taken responsibility.
2166      *
2167      * @return A collection of {@code Conference}s created by this {@code ConnectionService}.
2168      */
getAllConferences()2169     public final Collection<Conference> getAllConferences() {
2170         return mConferenceById.values();
2171     }
2172 
2173     /**
2174      * Create a {@code Connection} given an incoming request. This is used to attach to existing
2175      * incoming calls.
2176      *
2177      * @param connectionManagerPhoneAccount See description at
2178      *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
2179      * @param request Details about the incoming call.
2180      * @return The {@code Connection} object to satisfy this call, or {@code null} to
2181      *         not handle the call.
2182      */
onCreateIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)2183     public Connection onCreateIncomingConnection(
2184             PhoneAccountHandle connectionManagerPhoneAccount,
2185             ConnectionRequest request) {
2186         return null;
2187     }
2188 
2189     /**
2190      * Called after the {@link Connection} returned by
2191      * {@link #onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest)}
2192      * or {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)} has been
2193      * added to the {@link ConnectionService} and sent to Telecom.
2194      *
2195      * @param connection the {@link Connection}.
2196      * @hide
2197      */
onCreateConnectionComplete(Connection connection)2198     public void onCreateConnectionComplete(Connection connection) {
2199     }
2200 
2201     /**
2202      * Called by Telecom to inform the {@link ConnectionService} that its request to create a new
2203      * incoming {@link Connection} was denied.
2204      * <p>
2205      * Used when a self-managed {@link ConnectionService} attempts to create a new incoming
2206      * {@link Connection}, but Telecom has determined that the call cannot be allowed at this time.
2207      * The {@link ConnectionService} is responsible for silently rejecting the new incoming
2208      * {@link Connection}.
2209      * <p>
2210      * See {@link TelecomManager#isIncomingCallPermitted(PhoneAccountHandle)} for more information.
2211      *
2212      * @param connectionManagerPhoneAccount See description at
2213      *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
2214      * @param request The incoming connection request.
2215      */
onCreateIncomingConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)2216     public void onCreateIncomingConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount,
2217                                                  ConnectionRequest request) {
2218     }
2219 
2220     /**
2221      * Called by Telecom to inform the {@link ConnectionService} that its request to create a new
2222      * outgoing {@link Connection} was denied.
2223      * <p>
2224      * Used when a self-managed {@link ConnectionService} attempts to create a new outgoing
2225      * {@link Connection}, but Telecom has determined that the call cannot be placed at this time.
2226      * The {@link ConnectionService} is responisible for informing the user that the
2227      * {@link Connection} cannot be made at this time.
2228      * <p>
2229      * See {@link TelecomManager#isOutgoingCallPermitted(PhoneAccountHandle)} for more information.
2230      *
2231      * @param connectionManagerPhoneAccount See description at
2232      *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
2233      * @param request The outgoing connection request.
2234      */
onCreateOutgoingConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)2235     public void onCreateOutgoingConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount,
2236                                                  ConnectionRequest request) {
2237     }
2238 
2239     /**
2240      * Trigger recalculate functinality for conference calls. This is used when a Telephony
2241      * Connection is part of a conference controller but is not yet added to Connection
2242      * Service and hence cannot be added to the conference call.
2243      *
2244      * @hide
2245      */
triggerConferenceRecalculate()2246     public void triggerConferenceRecalculate() {
2247     }
2248 
2249     /**
2250      * Create a {@code Connection} given an outgoing request. This is used to initiate new
2251      * outgoing calls.
2252      *
2253      * @param connectionManagerPhoneAccount The connection manager account to use for managing
2254      *         this call.
2255      *         <p>
2256      *         If this parameter is not {@code null}, it means that this {@code ConnectionService}
2257      *         has registered one or more {@code PhoneAccount}s having
2258      *         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}. This parameter will contain
2259      *         one of these {@code PhoneAccount}s, while the {@code request} will contain another
2260      *         (usually but not always distinct) {@code PhoneAccount} to be used for actually
2261      *         making the connection.
2262      *         <p>
2263      *         If this parameter is {@code null}, it means that this {@code ConnectionService} is
2264      *         being asked to make a direct connection. The
2265      *         {@link ConnectionRequest#getAccountHandle()} of parameter {@code request} will be
2266      *         a {@code PhoneAccount} registered by this {@code ConnectionService} to use for
2267      *         making the connection.
2268      * @param request Details about the outgoing call.
2269      * @return The {@code Connection} object to satisfy this call, or the result of an invocation
2270      *         of {@link Connection#createFailedConnection(DisconnectCause)} to not handle the call.
2271      */
onCreateOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)2272     public Connection onCreateOutgoingConnection(
2273             PhoneAccountHandle connectionManagerPhoneAccount,
2274             ConnectionRequest request) {
2275         return null;
2276     }
2277 
2278     /**
2279      * Called by Telecom to request that a {@link ConnectionService} creates an instance of an
2280      * outgoing handover {@link Connection}.
2281      * <p>
2282      * A call handover is the process where an ongoing call is transferred from one app (i.e.
2283      * {@link ConnectionService} to another app.  The user could, for example, choose to continue a
2284      * mobile network call in a video calling app.  The mobile network call via the Telephony stack
2285      * is referred to as the source of the handover, and the video calling app is referred to as the
2286      * destination.
2287      * <p>
2288      * When considering a handover scenario the <em>initiating</em> device is where a user initiated
2289      * the handover process (e.g. by calling {@link android.telecom.Call#handoverTo(
2290      * PhoneAccountHandle, int, Bundle)}, and the other device is considered the <em>receiving</em>
2291      * device.
2292      * <p>
2293      * This method is called on the destination {@link ConnectionService} on <em>initiating</em>
2294      * device when the user initiates a handover request from one app to another.  The user request
2295      * originates in the {@link InCallService} via
2296      * {@link android.telecom.Call#handoverTo(PhoneAccountHandle, int, Bundle)}.
2297      * <p>
2298      * For a full discussion of the handover process and the APIs involved, see
2299      * {@link android.telecom.Call#handoverTo(PhoneAccountHandle, int, Bundle)}.
2300      * <p>
2301      * Implementations of this method should return an instance of {@link Connection} which
2302      * represents the handover.  If your app does not wish to accept a handover to it at this time,
2303      * you can return {@code null}.  The code below shows an example of how this is done.
2304      * <pre>
2305      * {@code
2306      * public Connection onCreateIncomingHandoverConnection(PhoneAccountHandle
2307      *     fromPhoneAccountHandle, ConnectionRequest request) {
2308      *   if (!isHandoverAvailable()) {
2309      *       return null;
2310      *   }
2311      *   MyConnection connection = new MyConnection();
2312      *   connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED);
2313      *   connection.setVideoState(request.getVideoState());
2314      *   return connection;
2315      * }
2316      * }
2317      * </pre>
2318      *
2319      * @param fromPhoneAccountHandle {@link PhoneAccountHandle} associated with the
2320      *                               ConnectionService which needs to handover the call.
2321      * @param request Details about the call to handover.
2322      * @return {@link Connection} instance corresponding to the handover call.
2323      */
onCreateOutgoingHandoverConnection(PhoneAccountHandle fromPhoneAccountHandle, ConnectionRequest request)2324     public Connection onCreateOutgoingHandoverConnection(PhoneAccountHandle fromPhoneAccountHandle,
2325                                                          ConnectionRequest request) {
2326         return null;
2327     }
2328 
2329     /**
2330      * Called by Telecom to request that a {@link ConnectionService} creates an instance of an
2331      * incoming handover {@link Connection}.
2332      * <p>
2333      * A call handover is the process where an ongoing call is transferred from one app (i.e.
2334      * {@link ConnectionService} to another app.  The user could, for example, choose to continue a
2335      * mobile network call in a video calling app.  The mobile network call via the Telephony stack
2336      * is referred to as the source of the handover, and the video calling app is referred to as the
2337      * destination.
2338      * <p>
2339      * When considering a handover scenario the <em>initiating</em> device is where a user initiated
2340      * the handover process (e.g. by calling {@link android.telecom.Call#handoverTo(
2341      * PhoneAccountHandle, int, Bundle)}, and the other device is considered the <em>receiving</em>
2342      * device.
2343      * <p>
2344      * This method is called on the destination app on the <em>receiving</em> device when the
2345      * destination app calls {@link TelecomManager#acceptHandover(Uri, int, PhoneAccountHandle)} to
2346      * accept an incoming handover from the <em>initiating</em> device.
2347      * <p>
2348      * For a full discussion of the handover process and the APIs involved, see
2349      * {@link android.telecom.Call#handoverTo(PhoneAccountHandle, int, Bundle)}.
2350      * <p>
2351      * Implementations of this method should return an instance of {@link Connection} which
2352      * represents the handover.  The code below shows an example of how this is done.
2353      * <pre>
2354      * {@code
2355      * public Connection onCreateIncomingHandoverConnection(PhoneAccountHandle
2356      *     fromPhoneAccountHandle, ConnectionRequest request) {
2357      *   // Given that your app requested to accept the handover, you should not return null here.
2358      *   MyConnection connection = new MyConnection();
2359      *   connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED);
2360      *   connection.setVideoState(request.getVideoState());
2361      *   return connection;
2362      * }
2363      * }
2364      * </pre>
2365      *
2366      * @param fromPhoneAccountHandle {@link PhoneAccountHandle} associated with the
2367      *                               ConnectionService which needs to handover the call.
2368      * @param request Details about the call which needs to be handover.
2369      * @return {@link Connection} instance corresponding to the handover call.
2370      */
onCreateIncomingHandoverConnection(PhoneAccountHandle fromPhoneAccountHandle, ConnectionRequest request)2371     public Connection onCreateIncomingHandoverConnection(PhoneAccountHandle fromPhoneAccountHandle,
2372                                                          ConnectionRequest request) {
2373         return null;
2374     }
2375 
2376     /**
2377      * Called by Telecom in response to a {@code TelecomManager#acceptHandover()}
2378      * invocation which failed.
2379      * <p>
2380      * For a full discussion of the handover process and the APIs involved, see
2381      * {@link android.telecom.Call#handoverTo(PhoneAccountHandle, int, Bundle)}
2382      *
2383      * @param request Details about the call which failed to handover.
2384      * @param error Reason for handover failure.  Will be one of the
2385      */
onHandoverFailed(ConnectionRequest request, @Call.Callback.HandoverFailureErrors int error)2386     public void onHandoverFailed(ConnectionRequest request,
2387             @Call.Callback.HandoverFailureErrors int error) {
2388         return;
2389     }
2390 
2391     /**
2392      * Create a {@code Connection} for a new unknown call. An unknown call is a call originating
2393      * from the ConnectionService that was neither a user-initiated outgoing call, nor an incoming
2394      * call created using
2395      * {@code TelecomManager#addNewIncomingCall(PhoneAccountHandle, android.os.Bundle)}.
2396      *
2397      * @hide
2398      */
onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)2399     public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount,
2400             ConnectionRequest request) {
2401         return null;
2402     }
2403 
2404     /**
2405      * Conference two specified connections. Invoked when the user has made a request to merge the
2406      * specified connections into a conference call. In response, the connection service should
2407      * create an instance of {@link Conference} and pass it into {@link #addConference}.
2408      *
2409      * @param connection1 A connection to merge into a conference call.
2410      * @param connection2 A connection to merge into a conference call.
2411      */
onConference(Connection connection1, Connection connection2)2412     public void onConference(Connection connection1, Connection connection2) {}
2413 
2414     /**
2415      * Called when a connection is added.
2416      * @hide
2417      */
onConnectionAdded(Connection connection)2418     public void onConnectionAdded(Connection connection) {}
2419 
2420     /**
2421      * Called when a connection is removed.
2422      * @hide
2423      */
onConnectionRemoved(Connection connection)2424     public void onConnectionRemoved(Connection connection) {}
2425 
2426     /**
2427      * Called when a conference is added.
2428      * @hide
2429      */
onConferenceAdded(Conference conference)2430     public void onConferenceAdded(Conference conference) {}
2431 
2432     /**
2433      * Called when a conference is removed.
2434      * @hide
2435      */
onConferenceRemoved(Conference conference)2436     public void onConferenceRemoved(Conference conference) {}
2437 
2438     /**
2439      * Indicates that a remote conference has been created for existing {@link RemoteConnection}s.
2440      * When this method is invoked, this {@link ConnectionService} should create its own
2441      * representation of the conference call and send it to telecom using {@link #addConference}.
2442      * <p>
2443      * This is only relevant to {@link ConnectionService}s which are registered with
2444      * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}.
2445      *
2446      * @param conference The remote conference call.
2447      */
onRemoteConferenceAdded(RemoteConference conference)2448     public void onRemoteConferenceAdded(RemoteConference conference) {}
2449 
2450     /**
2451      * Called when an existing connection is added remotely.
2452      * @param connection The existing connection which was added.
2453      */
onRemoteExistingConnectionAdded(RemoteConnection connection)2454     public void onRemoteExistingConnectionAdded(RemoteConnection connection) {}
2455 
2456     /**
2457      * Called when the {@link ConnectionService} has lost the call focus.
2458      * The {@link ConnectionService} should release the call resources and invokes
2459      * {@link ConnectionService#connectionServiceFocusReleased()} to inform telecom that it has
2460      * released the call resources.
2461      */
onConnectionServiceFocusLost()2462     public void onConnectionServiceFocusLost() {}
2463 
2464     /**
2465      * Called when the {@link ConnectionService} has gained the call focus. The
2466      * {@link ConnectionService} can acquire the call resources at this time.
2467      */
onConnectionServiceFocusGained()2468     public void onConnectionServiceFocusGained() {}
2469 
2470     /**
2471      * @hide
2472      */
containsConference(Conference conference)2473     public boolean containsConference(Conference conference) {
2474         return mIdByConference.containsKey(conference);
2475     }
2476 
2477     /** {@hide} */
addRemoteConference(RemoteConference remoteConference)2478     void addRemoteConference(RemoteConference remoteConference) {
2479         onRemoteConferenceAdded(remoteConference);
2480     }
2481 
2482     /** {@hide} */
addRemoteExistingConnection(RemoteConnection remoteConnection)2483     void addRemoteExistingConnection(RemoteConnection remoteConnection) {
2484         onRemoteExistingConnectionAdded(remoteConnection);
2485     }
2486 
onAccountsInitialized()2487     private void onAccountsInitialized() {
2488         mAreAccountsInitialized = true;
2489         for (Runnable r : mPreInitializationConnectionRequests) {
2490             r.run();
2491         }
2492         mPreInitializationConnectionRequests.clear();
2493     }
2494 
2495     /**
2496      * Adds an existing connection to the list of connections, identified by a new call ID unique
2497      * to this connection service.
2498      *
2499      * @param connection The connection.
2500      * @return The ID of the connection (e.g. the call-id).
2501      */
addExistingConnectionInternal(PhoneAccountHandle handle, Connection connection)2502     private String addExistingConnectionInternal(PhoneAccountHandle handle, Connection connection) {
2503         String id;
2504 
2505         if (connection.getExtras() != null && connection.getExtras()
2506                 .containsKey(Connection.EXTRA_ORIGINAL_CONNECTION_ID)) {
2507             id = connection.getExtras().getString(Connection.EXTRA_ORIGINAL_CONNECTION_ID);
2508             Log.d(this, "addExistingConnectionInternal - conn %s reusing original id %s",
2509                     connection.getTelecomCallId(), id);
2510         } else if (handle == null) {
2511             // If no phone account handle was provided, we cannot be sure the call ID is unique,
2512             // so just use a random UUID.
2513             id = UUID.randomUUID().toString();
2514         } else {
2515             // Phone account handle was provided, so use the ConnectionService class name as a
2516             // prefix for a unique incremental call ID.
2517             id = handle.getComponentName().getClassName() + "@" + getNextCallId();
2518         }
2519         addConnection(handle, id, connection);
2520         return id;
2521     }
2522 
addConnection(PhoneAccountHandle handle, String callId, Connection connection)2523     private void addConnection(PhoneAccountHandle handle, String callId, Connection connection) {
2524         connection.setTelecomCallId(callId);
2525         mConnectionById.put(callId, connection);
2526         mIdByConnection.put(connection, callId);
2527         connection.addConnectionListener(mConnectionListener);
2528         connection.setConnectionService(this);
2529         connection.setPhoneAccountHandle(handle);
2530         onConnectionAdded(connection);
2531     }
2532 
2533     /** {@hide} */
removeConnection(Connection connection)2534     protected void removeConnection(Connection connection) {
2535         connection.unsetConnectionService(this);
2536         connection.removeConnectionListener(mConnectionListener);
2537         String id = mIdByConnection.get(connection);
2538         if (id != null) {
2539             mConnectionById.remove(id);
2540             mIdByConnection.remove(connection);
2541             mAdapter.removeCall(id);
2542             onConnectionRemoved(connection);
2543         }
2544     }
2545 
addConferenceInternal(Conference conference)2546     private String addConferenceInternal(Conference conference) {
2547         String originalId = null;
2548         if (conference.getExtras() != null && conference.getExtras()
2549                 .containsKey(Connection.EXTRA_ORIGINAL_CONNECTION_ID)) {
2550             originalId = conference.getExtras().getString(Connection.EXTRA_ORIGINAL_CONNECTION_ID);
2551             Log.d(this, "addConferenceInternal: conf %s reusing original id %s",
2552                     conference.getTelecomCallId(),
2553                     originalId);
2554         }
2555         if (mIdByConference.containsKey(conference)) {
2556             Log.w(this, "Re-adding an existing conference: %s.", conference);
2557         } else if (conference != null) {
2558             // Conferences do not (yet) have a PhoneAccountHandle associated with them, so we
2559             // cannot determine a ConnectionService class name to associate with the ID, so use
2560             // a unique UUID (for now).
2561             String id = originalId == null ? UUID.randomUUID().toString() : originalId;
2562             mConferenceById.put(id, conference);
2563             mIdByConference.put(conference, id);
2564             conference.addListener(mConferenceListener);
2565             return id;
2566         }
2567 
2568         return null;
2569     }
2570 
removeConference(Conference conference)2571     private void removeConference(Conference conference) {
2572         if (mIdByConference.containsKey(conference)) {
2573             conference.removeListener(mConferenceListener);
2574 
2575             String id = mIdByConference.get(conference);
2576             mConferenceById.remove(id);
2577             mIdByConference.remove(conference);
2578             mAdapter.removeCall(id);
2579 
2580             onConferenceRemoved(conference);
2581         }
2582     }
2583 
findConnectionForAction(String callId, String action)2584     private Connection findConnectionForAction(String callId, String action) {
2585         if (callId != null && mConnectionById.containsKey(callId)) {
2586             return mConnectionById.get(callId);
2587         }
2588         Log.w(this, "%s - Cannot find Connection %s", action, callId);
2589         return getNullConnection();
2590     }
2591 
getNullConnection()2592     static synchronized Connection getNullConnection() {
2593         if (sNullConnection == null) {
2594             sNullConnection = new Connection() {};
2595         }
2596         return sNullConnection;
2597     }
2598 
findConferenceForAction(String conferenceId, String action)2599     private Conference findConferenceForAction(String conferenceId, String action) {
2600         if (mConferenceById.containsKey(conferenceId)) {
2601             return mConferenceById.get(conferenceId);
2602         }
2603         Log.w(this, "%s - Cannot find conference %s", action, conferenceId);
2604         return getNullConference();
2605     }
2606 
createConnectionIdList(List<Connection> connections)2607     private List<String> createConnectionIdList(List<Connection> connections) {
2608         List<String> ids = new ArrayList<>();
2609         for (Connection c : connections) {
2610             if (mIdByConnection.containsKey(c)) {
2611                 ids.add(mIdByConnection.get(c));
2612             }
2613         }
2614         Collections.sort(ids);
2615         return ids;
2616     }
2617 
2618     /**
2619      * Builds a list of {@link Connection} and {@link Conference} IDs based on the list of
2620      * {@link Conferenceable}s passed in.
2621      *
2622      * @param conferenceables The {@link Conferenceable} connections and conferences.
2623      * @return List of string conference and call Ids.
2624      */
createIdList(List<Conferenceable> conferenceables)2625     private List<String> createIdList(List<Conferenceable> conferenceables) {
2626         List<String> ids = new ArrayList<>();
2627         for (Conferenceable c : conferenceables) {
2628             // Only allow Connection and Conference conferenceables.
2629             if (c instanceof Connection) {
2630                 Connection connection = (Connection) c;
2631                 if (mIdByConnection.containsKey(connection)) {
2632                     ids.add(mIdByConnection.get(connection));
2633                 }
2634             } else if (c instanceof Conference) {
2635                 Conference conference = (Conference) c;
2636                 if (mIdByConference.containsKey(conference)) {
2637                     ids.add(mIdByConference.get(conference));
2638                 }
2639             }
2640         }
2641         Collections.sort(ids);
2642         return ids;
2643     }
2644 
getNullConference()2645     private Conference getNullConference() {
2646         if (sNullConference == null) {
2647             sNullConference = new Conference(null) {};
2648         }
2649         return sNullConference;
2650     }
2651 
endAllConnections()2652     private void endAllConnections() {
2653         // Unbound from telecomm.  We should end all connections and conferences.
2654         for (Connection connection : mIdByConnection.keySet()) {
2655             // only operate on top-level calls. Conference calls will be removed on their own.
2656             if (connection.getConference() == null) {
2657                 connection.onDisconnect();
2658             }
2659         }
2660         for (Conference conference : mIdByConference.keySet()) {
2661             conference.onDisconnect();
2662         }
2663     }
2664 
2665     /**
2666      * Retrieves the next call ID as maintainted by the connection service.
2667      *
2668      * @return The call ID.
2669      */
getNextCallId()2670     private int getNextCallId() {
2671         synchronized (mIdSyncRoot) {
2672             return ++mId;
2673         }
2674     }
2675 }
2676