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