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 
30 import com.android.internal.os.SomeArgs;
31 import com.android.internal.telecom.IConnectionService;
32 import com.android.internal.telecom.IConnectionServiceAdapter;
33 import com.android.internal.telecom.RemoteServiceCallback;
34 
35 import java.util.ArrayList;
36 import java.util.Collection;
37 import java.util.Collections;
38 import java.util.List;
39 import java.util.Map;
40 import java.util.UUID;
41 import java.util.concurrent.ConcurrentHashMap;
42 
43 /**
44  * An abstract service that should be implemented by any apps which can make phone calls (VoIP or
45  * otherwise) and want those calls to be integrated into the built-in phone app.
46  * Once implemented, the {@code ConnectionService} needs two additional steps before it will be
47  * integrated into the phone app:
48  * <p>
49  * 1. <i>Registration in AndroidManifest.xml</i>
50  * <br/>
51  * <pre>
52  * &lt;service android:name="com.example.package.MyConnectionService"
53  *    android:label="@string/some_label_for_my_connection_service"
54  *    android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"&gt;
55  *  &lt;intent-filter&gt;
56  *   &lt;action android:name="android.telecom.ConnectionService" /&gt;
57  *  &lt;/intent-filter&gt;
58  * &lt;/service&gt;
59  * </pre>
60  * <p>
61  * 2. <i> Registration of {@link PhoneAccount} with {@link TelecomManager}.</i>
62  * <br/>
63  * See {@link PhoneAccount} and {@link TelecomManager#registerPhoneAccount} for more information.
64  * <p>
65  * Once registered and enabled by the user in the phone app settings, telecom will bind to a
66  * {@code ConnectionService} implementation when it wants that {@code ConnectionService} to place
67  * a call or the service has indicated that is has an incoming call through
68  * {@link TelecomManager#addNewIncomingCall}. The {@code ConnectionService} can then expect a call
69  * to {@link #onCreateIncomingConnection} or {@link #onCreateOutgoingConnection} wherein it
70  * should provide a new instance of a {@link Connection} object.  It is through this
71  * {@link Connection} object that telecom receives state updates and the {@code ConnectionService}
72  * receives call-commands such as answer, reject, hold and disconnect.
73  * <p>
74  * When there are no more live calls, telecom will unbind from the {@code ConnectionService}.
75  */
76 public abstract class ConnectionService extends Service {
77     /**
78      * The {@link Intent} that must be declared as handled by the service.
79      */
80     @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
81     public static final String SERVICE_INTERFACE = "android.telecom.ConnectionService";
82 
83     // Flag controlling whether PII is emitted into the logs
84     private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG);
85 
86     private static final int MSG_ADD_CONNECTION_SERVICE_ADAPTER = 1;
87     private static final int MSG_CREATE_CONNECTION = 2;
88     private static final int MSG_ABORT = 3;
89     private static final int MSG_ANSWER = 4;
90     private static final int MSG_REJECT = 5;
91     private static final int MSG_DISCONNECT = 6;
92     private static final int MSG_HOLD = 7;
93     private static final int MSG_UNHOLD = 8;
94     private static final int MSG_ON_CALL_AUDIO_STATE_CHANGED = 9;
95     private static final int MSG_PLAY_DTMF_TONE = 10;
96     private static final int MSG_STOP_DTMF_TONE = 11;
97     private static final int MSG_CONFERENCE = 12;
98     private static final int MSG_SPLIT_FROM_CONFERENCE = 13;
99     private static final int MSG_ON_POST_DIAL_CONTINUE = 14;
100     private static final int MSG_REMOVE_CONNECTION_SERVICE_ADAPTER = 16;
101     private static final int MSG_ANSWER_VIDEO = 17;
102     private static final int MSG_MERGE_CONFERENCE = 18;
103     private static final int MSG_SWAP_CONFERENCE = 19;
104     private static final int MSG_REJECT_WITH_MESSAGE = 20;
105     private static final int MSG_SILENCE = 21;
106     private static final int MSG_PULL_EXTERNAL_CALL = 22;
107     private static final int MSG_SEND_CALL_EVENT = 23;
108     private static final int MSG_ON_EXTRAS_CHANGED = 24;
109 
110     private static Connection sNullConnection;
111 
112     private final Map<String, Connection> mConnectionById = new ConcurrentHashMap<>();
113     private final Map<Connection, String> mIdByConnection = new ConcurrentHashMap<>();
114     private final Map<String, Conference> mConferenceById = new ConcurrentHashMap<>();
115     private final Map<Conference, String> mIdByConference = new ConcurrentHashMap<>();
116     private final RemoteConnectionManager mRemoteConnectionManager =
117             new RemoteConnectionManager(this);
118     private final List<Runnable> mPreInitializationConnectionRequests = new ArrayList<>();
119     private final ConnectionServiceAdapter mAdapter = new ConnectionServiceAdapter();
120 
121     private boolean mAreAccountsInitialized = false;
122     private Conference sNullConference;
123     private Object mIdSyncRoot = new Object();
124     private int mId = 0;
125 
126     private final IBinder mBinder = new IConnectionService.Stub() {
127         @Override
128         public void addConnectionServiceAdapter(IConnectionServiceAdapter adapter) {
129             mHandler.obtainMessage(MSG_ADD_CONNECTION_SERVICE_ADAPTER, adapter).sendToTarget();
130         }
131 
132         public void removeConnectionServiceAdapter(IConnectionServiceAdapter adapter) {
133             mHandler.obtainMessage(MSG_REMOVE_CONNECTION_SERVICE_ADAPTER, adapter).sendToTarget();
134         }
135 
136         @Override
137         public void createConnection(
138                 PhoneAccountHandle connectionManagerPhoneAccount,
139                 String id,
140                 ConnectionRequest request,
141                 boolean isIncoming,
142                 boolean isUnknown) {
143             SomeArgs args = SomeArgs.obtain();
144             args.arg1 = connectionManagerPhoneAccount;
145             args.arg2 = id;
146             args.arg3 = request;
147             args.argi1 = isIncoming ? 1 : 0;
148             args.argi2 = isUnknown ? 1 : 0;
149             mHandler.obtainMessage(MSG_CREATE_CONNECTION, args).sendToTarget();
150         }
151 
152         @Override
153         public void abort(String callId) {
154             mHandler.obtainMessage(MSG_ABORT, callId).sendToTarget();
155         }
156 
157         @Override
158         public void answerVideo(String callId, int videoState) {
159             SomeArgs args = SomeArgs.obtain();
160             args.arg1 = callId;
161             args.argi1 = videoState;
162             mHandler.obtainMessage(MSG_ANSWER_VIDEO, args).sendToTarget();
163         }
164 
165         @Override
166         public void answer(String callId) {
167             mHandler.obtainMessage(MSG_ANSWER, callId).sendToTarget();
168         }
169 
170         @Override
171         public void reject(String callId) {
172             mHandler.obtainMessage(MSG_REJECT, callId).sendToTarget();
173         }
174 
175         @Override
176         public void rejectWithMessage(String callId, String message) {
177             SomeArgs args = SomeArgs.obtain();
178             args.arg1 = callId;
179             args.arg2 = message;
180             mHandler.obtainMessage(MSG_REJECT_WITH_MESSAGE, args).sendToTarget();
181         }
182 
183         @Override
184         public void silence(String callId) {
185             mHandler.obtainMessage(MSG_SILENCE, callId).sendToTarget();
186         }
187 
188         @Override
189         public void disconnect(String callId) {
190             mHandler.obtainMessage(MSG_DISCONNECT, callId).sendToTarget();
191         }
192 
193         @Override
194         public void hold(String callId) {
195             mHandler.obtainMessage(MSG_HOLD, callId).sendToTarget();
196         }
197 
198         @Override
199         public void unhold(String callId) {
200             mHandler.obtainMessage(MSG_UNHOLD, callId).sendToTarget();
201         }
202 
203         @Override
204         public void onCallAudioStateChanged(String callId, CallAudioState callAudioState) {
205             SomeArgs args = SomeArgs.obtain();
206             args.arg1 = callId;
207             args.arg2 = callAudioState;
208             mHandler.obtainMessage(MSG_ON_CALL_AUDIO_STATE_CHANGED, args).sendToTarget();
209         }
210 
211         @Override
212         public void playDtmfTone(String callId, char digit) {
213             mHandler.obtainMessage(MSG_PLAY_DTMF_TONE, digit, 0, callId).sendToTarget();
214         }
215 
216         @Override
217         public void stopDtmfTone(String callId) {
218             mHandler.obtainMessage(MSG_STOP_DTMF_TONE, callId).sendToTarget();
219         }
220 
221         @Override
222         public void conference(String callId1, String callId2) {
223             SomeArgs args = SomeArgs.obtain();
224             args.arg1 = callId1;
225             args.arg2 = callId2;
226             mHandler.obtainMessage(MSG_CONFERENCE, args).sendToTarget();
227         }
228 
229         @Override
230         public void splitFromConference(String callId) {
231             mHandler.obtainMessage(MSG_SPLIT_FROM_CONFERENCE, callId).sendToTarget();
232         }
233 
234         @Override
235         public void mergeConference(String callId) {
236             mHandler.obtainMessage(MSG_MERGE_CONFERENCE, callId).sendToTarget();
237         }
238 
239         @Override
240         public void swapConference(String callId) {
241             mHandler.obtainMessage(MSG_SWAP_CONFERENCE, callId).sendToTarget();
242         }
243 
244         @Override
245         public void onPostDialContinue(String callId, boolean proceed) {
246             SomeArgs args = SomeArgs.obtain();
247             args.arg1 = callId;
248             args.argi1 = proceed ? 1 : 0;
249             mHandler.obtainMessage(MSG_ON_POST_DIAL_CONTINUE, args).sendToTarget();
250         }
251 
252         @Override
253         public void pullExternalCall(String callId) {
254             mHandler.obtainMessage(MSG_PULL_EXTERNAL_CALL, callId).sendToTarget();
255         }
256 
257         @Override
258         public void sendCallEvent(String callId, String event, Bundle extras) {
259             SomeArgs args = SomeArgs.obtain();
260             args.arg1 = callId;
261             args.arg2 = event;
262             args.arg3 = extras;
263             mHandler.obtainMessage(MSG_SEND_CALL_EVENT, args).sendToTarget();
264         }
265 
266         @Override
267         public void onExtrasChanged(String callId, Bundle extras) {
268             SomeArgs args = SomeArgs.obtain();
269             args.arg1 = callId;
270             args.arg2 = extras;
271             mHandler.obtainMessage(MSG_ON_EXTRAS_CHANGED, args).sendToTarget();
272         }
273     };
274 
275     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
276         @Override
277         public void handleMessage(Message msg) {
278             switch (msg.what) {
279                 case MSG_ADD_CONNECTION_SERVICE_ADAPTER:
280                     mAdapter.addAdapter((IConnectionServiceAdapter) msg.obj);
281                     onAdapterAttached();
282                     break;
283                 case MSG_REMOVE_CONNECTION_SERVICE_ADAPTER:
284                     mAdapter.removeAdapter((IConnectionServiceAdapter) msg.obj);
285                     break;
286                 case MSG_CREATE_CONNECTION: {
287                     SomeArgs args = (SomeArgs) msg.obj;
288                     try {
289                         final PhoneAccountHandle connectionManagerPhoneAccount =
290                                 (PhoneAccountHandle) args.arg1;
291                         final String id = (String) args.arg2;
292                         final ConnectionRequest request = (ConnectionRequest) args.arg3;
293                         final boolean isIncoming = args.argi1 == 1;
294                         final boolean isUnknown = args.argi2 == 1;
295                         if (!mAreAccountsInitialized) {
296                             Log.d(this, "Enqueueing pre-init request %s", id);
297                             mPreInitializationConnectionRequests.add(new Runnable() {
298                                 @Override
299                                 public void run() {
300                                     createConnection(
301                                             connectionManagerPhoneAccount,
302                                             id,
303                                             request,
304                                             isIncoming,
305                                             isUnknown);
306                                 }
307                             });
308                         } else {
309                             createConnection(
310                                     connectionManagerPhoneAccount,
311                                     id,
312                                     request,
313                                     isIncoming,
314                                     isUnknown);
315                         }
316                     } finally {
317                         args.recycle();
318                     }
319                     break;
320                 }
321                 case MSG_ABORT:
322                     abort((String) msg.obj);
323                     break;
324                 case MSG_ANSWER:
325                     answer((String) msg.obj);
326                     break;
327                 case MSG_ANSWER_VIDEO: {
328                     SomeArgs args = (SomeArgs) msg.obj;
329                     try {
330                         String callId = (String) args.arg1;
331                         int videoState = args.argi1;
332                         answerVideo(callId, videoState);
333                     } finally {
334                         args.recycle();
335                     }
336                     break;
337                 }
338                 case MSG_REJECT:
339                     reject((String) msg.obj);
340                     break;
341                 case MSG_REJECT_WITH_MESSAGE: {
342                     SomeArgs args = (SomeArgs) msg.obj;
343                     try {
344                         reject((String) args.arg1, (String) args.arg2);
345                     } finally {
346                         args.recycle();
347                     }
348                     break;
349                 }
350                 case MSG_DISCONNECT:
351                     disconnect((String) msg.obj);
352                     break;
353                 case MSG_SILENCE:
354                     silence((String) msg.obj);
355                     break;
356                 case MSG_HOLD:
357                     hold((String) msg.obj);
358                     break;
359                 case MSG_UNHOLD:
360                     unhold((String) msg.obj);
361                     break;
362                 case MSG_ON_CALL_AUDIO_STATE_CHANGED: {
363                     SomeArgs args = (SomeArgs) msg.obj;
364                     try {
365                         String callId = (String) args.arg1;
366                         CallAudioState audioState = (CallAudioState) args.arg2;
367                         onCallAudioStateChanged(callId, new CallAudioState(audioState));
368                     } finally {
369                         args.recycle();
370                     }
371                     break;
372                 }
373                 case MSG_PLAY_DTMF_TONE:
374                     playDtmfTone((String) msg.obj, (char) msg.arg1);
375                     break;
376                 case MSG_STOP_DTMF_TONE:
377                     stopDtmfTone((String) msg.obj);
378                     break;
379                 case MSG_CONFERENCE: {
380                     SomeArgs args = (SomeArgs) msg.obj;
381                     try {
382                         String callId1 = (String) args.arg1;
383                         String callId2 = (String) args.arg2;
384                         conference(callId1, callId2);
385                     } finally {
386                         args.recycle();
387                     }
388                     break;
389                 }
390                 case MSG_SPLIT_FROM_CONFERENCE:
391                     splitFromConference((String) msg.obj);
392                     break;
393                 case MSG_MERGE_CONFERENCE:
394                     mergeConference((String) msg.obj);
395                     break;
396                 case MSG_SWAP_CONFERENCE:
397                     swapConference((String) msg.obj);
398                     break;
399                 case MSG_ON_POST_DIAL_CONTINUE: {
400                     SomeArgs args = (SomeArgs) msg.obj;
401                     try {
402                         String callId = (String) args.arg1;
403                         boolean proceed = (args.argi1 == 1);
404                         onPostDialContinue(callId, proceed);
405                     } finally {
406                         args.recycle();
407                     }
408                     break;
409                 }
410                 case MSG_PULL_EXTERNAL_CALL: {
411                     pullExternalCall((String) msg.obj);
412                     break;
413                 }
414                 case MSG_SEND_CALL_EVENT: {
415                     SomeArgs args = (SomeArgs) msg.obj;
416                     try {
417                         String callId = (String) args.arg1;
418                         String event = (String) args.arg2;
419                         Bundle extras = (Bundle) args.arg3;
420                         sendCallEvent(callId, event, extras);
421                     } finally {
422                         args.recycle();
423                     }
424                     break;
425                 }
426                 case MSG_ON_EXTRAS_CHANGED: {
427                     SomeArgs args = (SomeArgs) msg.obj;
428                     try {
429                         String callId = (String) args.arg1;
430                         Bundle extras = (Bundle) args.arg2;
431                         handleExtrasChanged(callId, extras);
432                     } finally {
433                         args.recycle();
434                     }
435                     break;
436                 }
437                 default:
438                     break;
439             }
440         }
441     };
442 
443     private final Conference.Listener mConferenceListener = new Conference.Listener() {
444         @Override
445         public void onStateChanged(Conference conference, int oldState, int newState) {
446             String id = mIdByConference.get(conference);
447             switch (newState) {
448                 case Connection.STATE_ACTIVE:
449                     mAdapter.setActive(id);
450                     break;
451                 case Connection.STATE_HOLDING:
452                     mAdapter.setOnHold(id);
453                     break;
454                 case Connection.STATE_DISCONNECTED:
455                     // handled by onDisconnected
456                     break;
457             }
458         }
459 
460         @Override
461         public void onDisconnected(Conference conference, DisconnectCause disconnectCause) {
462             String id = mIdByConference.get(conference);
463             mAdapter.setDisconnected(id, disconnectCause);
464         }
465 
466         @Override
467         public void onConnectionAdded(Conference conference, Connection connection) {
468         }
469 
470         @Override
471         public void onConnectionRemoved(Conference conference, Connection connection) {
472         }
473 
474         @Override
475         public void onConferenceableConnectionsChanged(
476                 Conference conference, List<Connection> conferenceableConnections) {
477             mAdapter.setConferenceableConnections(
478                     mIdByConference.get(conference),
479                     createConnectionIdList(conferenceableConnections));
480         }
481 
482         @Override
483         public void onDestroyed(Conference conference) {
484             removeConference(conference);
485         }
486 
487         @Override
488         public void onConnectionCapabilitiesChanged(
489                 Conference conference,
490                 int connectionCapabilities) {
491             String id = mIdByConference.get(conference);
492             Log.d(this, "call capabilities: conference: %s",
493                     Connection.capabilitiesToString(connectionCapabilities));
494             mAdapter.setConnectionCapabilities(id, connectionCapabilities);
495         }
496 
497         @Override
498         public void onConnectionPropertiesChanged(
499                 Conference conference,
500                 int connectionProperties) {
501             String id = mIdByConference.get(conference);
502             Log.d(this, "call capabilities: conference: %s",
503                     Connection.propertiesToString(connectionProperties));
504             mAdapter.setConnectionProperties(id, connectionProperties);
505         }
506 
507         @Override
508         public void onVideoStateChanged(Conference c, int videoState) {
509             String id = mIdByConference.get(c);
510             Log.d(this, "onVideoStateChanged set video state %d", videoState);
511             mAdapter.setVideoState(id, videoState);
512         }
513 
514         @Override
515         public void onVideoProviderChanged(Conference c, Connection.VideoProvider videoProvider) {
516             String id = mIdByConference.get(c);
517             Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c,
518                     videoProvider);
519             mAdapter.setVideoProvider(id, videoProvider);
520         }
521 
522         @Override
523         public void onStatusHintsChanged(Conference conference, StatusHints statusHints) {
524             String id = mIdByConference.get(conference);
525             if (id != null) {
526                 mAdapter.setStatusHints(id, statusHints);
527             }
528         }
529 
530         @Override
531         public void onExtrasChanged(Conference c, Bundle extras) {
532             String id = mIdByConference.get(c);
533             if (id != null) {
534                 mAdapter.putExtras(id, extras);
535             }
536         }
537 
538         @Override
539         public void onExtrasRemoved(Conference c, List<String> keys) {
540             String id = mIdByConference.get(c);
541             if (id != null) {
542                 mAdapter.removeExtras(id, keys);
543             }
544         }
545     };
546 
547     private final Connection.Listener mConnectionListener = new Connection.Listener() {
548         @Override
549         public void onStateChanged(Connection c, int state) {
550             String id = mIdByConnection.get(c);
551             Log.d(this, "Adapter set state %s %s", id, Connection.stateToString(state));
552             switch (state) {
553                 case Connection.STATE_ACTIVE:
554                     mAdapter.setActive(id);
555                     break;
556                 case Connection.STATE_DIALING:
557                     mAdapter.setDialing(id);
558                     break;
559                 case Connection.STATE_DISCONNECTED:
560                     // Handled in onDisconnected()
561                     break;
562                 case Connection.STATE_HOLDING:
563                     mAdapter.setOnHold(id);
564                     break;
565                 case Connection.STATE_NEW:
566                     // Nothing to tell Telecom
567                     break;
568                 case Connection.STATE_RINGING:
569                     mAdapter.setRinging(id);
570                     break;
571             }
572         }
573 
574         @Override
575         public void onDisconnected(Connection c, DisconnectCause disconnectCause) {
576             String id = mIdByConnection.get(c);
577             Log.d(this, "Adapter set disconnected %s", disconnectCause);
578             mAdapter.setDisconnected(id, disconnectCause);
579         }
580 
581         @Override
582         public void onVideoStateChanged(Connection c, int videoState) {
583             String id = mIdByConnection.get(c);
584             Log.d(this, "Adapter set video state %d", videoState);
585             mAdapter.setVideoState(id, videoState);
586         }
587 
588         @Override
589         public void onAddressChanged(Connection c, Uri address, int presentation) {
590             String id = mIdByConnection.get(c);
591             mAdapter.setAddress(id, address, presentation);
592         }
593 
594         @Override
595         public void onCallerDisplayNameChanged(
596                 Connection c, String callerDisplayName, int presentation) {
597             String id = mIdByConnection.get(c);
598             mAdapter.setCallerDisplayName(id, callerDisplayName, presentation);
599         }
600 
601         @Override
602         public void onDestroyed(Connection c) {
603             removeConnection(c);
604         }
605 
606         @Override
607         public void onPostDialWait(Connection c, String remaining) {
608             String id = mIdByConnection.get(c);
609             Log.d(this, "Adapter onPostDialWait %s, %s", c, remaining);
610             mAdapter.onPostDialWait(id, remaining);
611         }
612 
613         @Override
614         public void onPostDialChar(Connection c, char nextChar) {
615             String id = mIdByConnection.get(c);
616             Log.d(this, "Adapter onPostDialChar %s, %s", c, nextChar);
617             mAdapter.onPostDialChar(id, nextChar);
618         }
619 
620         @Override
621         public void onRingbackRequested(Connection c, boolean ringback) {
622             String id = mIdByConnection.get(c);
623             Log.d(this, "Adapter onRingback %b", ringback);
624             mAdapter.setRingbackRequested(id, ringback);
625         }
626 
627         @Override
628         public void onConnectionCapabilitiesChanged(Connection c, int capabilities) {
629             String id = mIdByConnection.get(c);
630             Log.d(this, "capabilities: parcelableconnection: %s",
631                     Connection.capabilitiesToString(capabilities));
632             mAdapter.setConnectionCapabilities(id, capabilities);
633         }
634 
635         @Override
636         public void onConnectionPropertiesChanged(Connection c, int properties) {
637             String id = mIdByConnection.get(c);
638             Log.d(this, "properties: parcelableconnection: %s",
639                     Connection.propertiesToString(properties));
640             mAdapter.setConnectionProperties(id, properties);
641         }
642 
643         @Override
644         public void onVideoProviderChanged(Connection c, Connection.VideoProvider videoProvider) {
645             String id = mIdByConnection.get(c);
646             Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c,
647                     videoProvider);
648             mAdapter.setVideoProvider(id, videoProvider);
649         }
650 
651         @Override
652         public void onAudioModeIsVoipChanged(Connection c, boolean isVoip) {
653             String id = mIdByConnection.get(c);
654             mAdapter.setIsVoipAudioMode(id, isVoip);
655         }
656 
657         @Override
658         public void onStatusHintsChanged(Connection c, StatusHints statusHints) {
659             String id = mIdByConnection.get(c);
660             mAdapter.setStatusHints(id, statusHints);
661         }
662 
663         @Override
664         public void onConferenceablesChanged(
665                 Connection connection, List<Conferenceable> conferenceables) {
666             mAdapter.setConferenceableConnections(
667                     mIdByConnection.get(connection),
668                     createIdList(conferenceables));
669         }
670 
671         @Override
672         public void onConferenceChanged(Connection connection, Conference conference) {
673             String id = mIdByConnection.get(connection);
674             if (id != null) {
675                 String conferenceId = null;
676                 if (conference != null) {
677                     conferenceId = mIdByConference.get(conference);
678                 }
679                 mAdapter.setIsConferenced(id, conferenceId);
680             }
681         }
682 
683         @Override
684         public void onConferenceMergeFailed(Connection connection) {
685             String id = mIdByConnection.get(connection);
686             if (id != null) {
687                 mAdapter.onConferenceMergeFailed(id);
688             }
689         }
690 
691         @Override
692         public void onExtrasChanged(Connection c, Bundle extras) {
693             String id = mIdByConnection.get(c);
694             if (id != null) {
695                 mAdapter.putExtras(id, extras);
696             }
697         }
698 
699         public void onExtrasRemoved(Connection c, List<String> keys) {
700             String id = mIdByConnection.get(c);
701             if (id != null) {
702                 mAdapter.removeExtras(id, keys);
703             }
704         }
705 
706 
707         @Override
708         public void onConnectionEvent(Connection connection, String event, Bundle extras) {
709             String id = mIdByConnection.get(connection);
710             if (id != null) {
711                 mAdapter.onConnectionEvent(id, event, extras);
712             }
713         }
714     };
715 
716     /** {@inheritDoc} */
717     @Override
onBind(Intent intent)718     public final IBinder onBind(Intent intent) {
719         return mBinder;
720     }
721 
722     /** {@inheritDoc} */
723     @Override
onUnbind(Intent intent)724     public boolean onUnbind(Intent intent) {
725         endAllConnections();
726         return super.onUnbind(intent);
727     }
728 
729     /**
730      * This can be used by telecom to either create a new outgoing call or attach to an existing
731      * incoming call. In either case, telecom will cycle through a set of services and call
732      * createConnection util a connection service cancels the process or completes it successfully.
733      */
createConnection( final PhoneAccountHandle callManagerAccount, final String callId, final ConnectionRequest request, boolean isIncoming, boolean isUnknown)734     private void createConnection(
735             final PhoneAccountHandle callManagerAccount,
736             final String callId,
737             final ConnectionRequest request,
738             boolean isIncoming,
739             boolean isUnknown) {
740         Log.d(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " +
741                         "isIncoming: %b, isUnknown: %b", callManagerAccount, callId, request,
742                 isIncoming,
743                 isUnknown);
744 
745         Connection connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request)
746                 : isIncoming ? onCreateIncomingConnection(callManagerAccount, request)
747                 : onCreateOutgoingConnection(callManagerAccount, request);
748         Log.d(this, "createConnection, connection: %s", connection);
749         if (connection == null) {
750             connection = Connection.createFailedConnection(
751                     new DisconnectCause(DisconnectCause.ERROR));
752         }
753 
754         connection.setTelecomCallId(callId);
755         if (connection.getState() != Connection.STATE_DISCONNECTED) {
756             addConnection(callId, connection);
757         }
758 
759         Uri address = connection.getAddress();
760         String number = address == null ? "null" : address.getSchemeSpecificPart();
761         Log.v(this, "createConnection, number: %s, state: %s, capabilities: %s, properties: %s",
762                 Connection.toLogSafePhoneNumber(number),
763                 Connection.stateToString(connection.getState()),
764                 Connection.capabilitiesToString(connection.getConnectionCapabilities()),
765                 Connection.propertiesToString(connection.getConnectionProperties()));
766 
767         Log.d(this, "createConnection, calling handleCreateConnectionSuccessful %s", callId);
768         mAdapter.handleCreateConnectionComplete(
769                 callId,
770                 request,
771                 new ParcelableConnection(
772                         request.getAccountHandle(),
773                         connection.getState(),
774                         connection.getConnectionCapabilities(),
775                         connection.getConnectionProperties(),
776                         connection.getAddress(),
777                         connection.getAddressPresentation(),
778                         connection.getCallerDisplayName(),
779                         connection.getCallerDisplayNamePresentation(),
780                         connection.getVideoProvider() == null ?
781                                 null : connection.getVideoProvider().getInterface(),
782                         connection.getVideoState(),
783                         connection.isRingbackRequested(),
784                         connection.getAudioModeIsVoip(),
785                         connection.getConnectTimeMillis(),
786                         connection.getStatusHints(),
787                         connection.getDisconnectCause(),
788                         createIdList(connection.getConferenceables()),
789                         connection.getExtras()));
790         if (isUnknown) {
791             triggerConferenceRecalculate();
792         }
793     }
794 
abort(String callId)795     private void abort(String callId) {
796         Log.d(this, "abort %s", callId);
797         findConnectionForAction(callId, "abort").onAbort();
798     }
799 
answerVideo(String callId, int videoState)800     private void answerVideo(String callId, int videoState) {
801         Log.d(this, "answerVideo %s", callId);
802         findConnectionForAction(callId, "answer").onAnswer(videoState);
803     }
804 
answer(String callId)805     private void answer(String callId) {
806         Log.d(this, "answer %s", callId);
807         findConnectionForAction(callId, "answer").onAnswer();
808     }
809 
reject(String callId)810     private void reject(String callId) {
811         Log.d(this, "reject %s", callId);
812         findConnectionForAction(callId, "reject").onReject();
813     }
814 
reject(String callId, String rejectWithMessage)815     private void reject(String callId, String rejectWithMessage) {
816         Log.d(this, "reject %s with message", callId);
817         findConnectionForAction(callId, "reject").onReject(rejectWithMessage);
818     }
819 
silence(String callId)820     private void silence(String callId) {
821         Log.d(this, "silence %s", callId);
822         findConnectionForAction(callId, "silence").onSilence();
823     }
824 
disconnect(String callId)825     private void disconnect(String callId) {
826         Log.d(this, "disconnect %s", callId);
827         if (mConnectionById.containsKey(callId)) {
828             findConnectionForAction(callId, "disconnect").onDisconnect();
829         } else {
830             findConferenceForAction(callId, "disconnect").onDisconnect();
831         }
832     }
833 
hold(String callId)834     private void hold(String callId) {
835         Log.d(this, "hold %s", callId);
836         if (mConnectionById.containsKey(callId)) {
837             findConnectionForAction(callId, "hold").onHold();
838         } else {
839             findConferenceForAction(callId, "hold").onHold();
840         }
841     }
842 
unhold(String callId)843     private void unhold(String callId) {
844         Log.d(this, "unhold %s", callId);
845         if (mConnectionById.containsKey(callId)) {
846             findConnectionForAction(callId, "unhold").onUnhold();
847         } else {
848             findConferenceForAction(callId, "unhold").onUnhold();
849         }
850     }
851 
onCallAudioStateChanged(String callId, CallAudioState callAudioState)852     private void onCallAudioStateChanged(String callId, CallAudioState callAudioState) {
853         Log.d(this, "onAudioStateChanged %s %s", callId, callAudioState);
854         if (mConnectionById.containsKey(callId)) {
855             findConnectionForAction(callId, "onCallAudioStateChanged").setCallAudioState(
856                     callAudioState);
857         } else {
858             findConferenceForAction(callId, "onCallAudioStateChanged").setCallAudioState(
859                     callAudioState);
860         }
861     }
862 
playDtmfTone(String callId, char digit)863     private void playDtmfTone(String callId, char digit) {
864         Log.d(this, "playDtmfTone %s %c", callId, digit);
865         if (mConnectionById.containsKey(callId)) {
866             findConnectionForAction(callId, "playDtmfTone").onPlayDtmfTone(digit);
867         } else {
868             findConferenceForAction(callId, "playDtmfTone").onPlayDtmfTone(digit);
869         }
870     }
871 
stopDtmfTone(String callId)872     private void stopDtmfTone(String callId) {
873         Log.d(this, "stopDtmfTone %s", callId);
874         if (mConnectionById.containsKey(callId)) {
875             findConnectionForAction(callId, "stopDtmfTone").onStopDtmfTone();
876         } else {
877             findConferenceForAction(callId, "stopDtmfTone").onStopDtmfTone();
878         }
879     }
880 
conference(String callId1, String callId2)881     private void conference(String callId1, String callId2) {
882         Log.d(this, "conference %s, %s", callId1, callId2);
883 
884         // Attempt to get second connection or conference.
885         Connection connection2 = findConnectionForAction(callId2, "conference");
886         Conference conference2 = getNullConference();
887         if (connection2 == getNullConnection()) {
888             conference2 = findConferenceForAction(callId2, "conference");
889             if (conference2 == getNullConference()) {
890                 Log.w(this, "Connection2 or Conference2 missing in conference request %s.",
891                         callId2);
892                 return;
893             }
894         }
895 
896         // Attempt to get first connection or conference and perform merge.
897         Connection connection1 = findConnectionForAction(callId1, "conference");
898         if (connection1 == getNullConnection()) {
899             Conference conference1 = findConferenceForAction(callId1, "addConnection");
900             if (conference1 == getNullConference()) {
901                 Log.w(this,
902                         "Connection1 or Conference1 missing in conference request %s.",
903                         callId1);
904             } else {
905                 // Call 1 is a conference.
906                 if (connection2 != getNullConnection()) {
907                     // Call 2 is a connection so merge via call 1 (conference).
908                     conference1.onMerge(connection2);
909                 } else {
910                     // Call 2 is ALSO a conference; this should never happen.
911                     Log.wtf(this, "There can only be one conference and an attempt was made to " +
912                             "merge two conferences.");
913                     return;
914                 }
915             }
916         } else {
917             // Call 1 is a connection.
918             if (conference2 != getNullConference()) {
919                 // Call 2 is a conference, so merge via call 2.
920                 conference2.onMerge(connection1);
921             } else {
922                 // Call 2 is a connection, so merge together.
923                 onConference(connection1, connection2);
924             }
925         }
926     }
927 
splitFromConference(String callId)928     private void splitFromConference(String callId) {
929         Log.d(this, "splitFromConference(%s)", callId);
930 
931         Connection connection = findConnectionForAction(callId, "splitFromConference");
932         if (connection == getNullConnection()) {
933             Log.w(this, "Connection missing in conference request %s.", callId);
934             return;
935         }
936 
937         Conference conference = connection.getConference();
938         if (conference != null) {
939             conference.onSeparate(connection);
940         }
941     }
942 
mergeConference(String callId)943     private void mergeConference(String callId) {
944         Log.d(this, "mergeConference(%s)", callId);
945         Conference conference = findConferenceForAction(callId, "mergeConference");
946         if (conference != null) {
947             conference.onMerge();
948         }
949     }
950 
swapConference(String callId)951     private void swapConference(String callId) {
952         Log.d(this, "swapConference(%s)", callId);
953         Conference conference = findConferenceForAction(callId, "swapConference");
954         if (conference != null) {
955             conference.onSwap();
956         }
957     }
958 
959     /**
960      * Notifies a {@link Connection} of a request to pull an external call.
961      *
962      * See {@link Call#pullExternalCall()}.
963      *
964      * @param callId The ID of the call to pull.
965      */
pullExternalCall(String callId)966     private void pullExternalCall(String callId) {
967         Log.d(this, "pullExternalCall(%s)", callId);
968         Connection connection = findConnectionForAction(callId, "pullExternalCall");
969         if (connection != null) {
970             connection.onPullExternalCall();
971         }
972     }
973 
974     /**
975      * Notifies a {@link Connection} of a call event.
976      *
977      * See {@link Call#sendCallEvent(String, Bundle)}.
978      *
979      * @param callId The ID of the call receiving the event.
980      * @param event The event.
981      * @param extras Extras associated with the event.
982      */
sendCallEvent(String callId, String event, Bundle extras)983     private void sendCallEvent(String callId, String event, Bundle extras) {
984         Log.d(this, "sendCallEvent(%s, %s)", callId, event);
985         Connection connection = findConnectionForAction(callId, "sendCallEvent");
986         if (connection != null) {
987             connection.onCallEvent(event, extras);
988         }
989 
990     }
991 
992     /**
993      * Notifies a {@link Connection} or {@link Conference} of a change to the extras from Telecom.
994      * <p>
995      * These extra changes can originate from Telecom itself, or from an {@link InCallService} via
996      * the {@link android.telecom.Call#putExtra(String, boolean)},
997      * {@link android.telecom.Call#putExtra(String, int)},
998      * {@link android.telecom.Call#putExtra(String, String)},
999      * {@link Call#removeExtras(List)}.
1000      *
1001      * @param callId The ID of the call receiving the event.
1002      * @param extras The new extras bundle.
1003      */
handleExtrasChanged(String callId, Bundle extras)1004     private void handleExtrasChanged(String callId, Bundle extras) {
1005         Log.d(this, "handleExtrasChanged(%s, %s)", callId, extras);
1006         if (mConnectionById.containsKey(callId)) {
1007             findConnectionForAction(callId, "handleExtrasChanged").handleExtrasChanged(extras);
1008         } else if (mConferenceById.containsKey(callId)) {
1009             findConferenceForAction(callId, "handleExtrasChanged").handleExtrasChanged(extras);
1010         }
1011     }
1012 
onPostDialContinue(String callId, boolean proceed)1013     private void onPostDialContinue(String callId, boolean proceed) {
1014         Log.d(this, "onPostDialContinue(%s)", callId);
1015         findConnectionForAction(callId, "stopDtmfTone").onPostDialContinue(proceed);
1016     }
1017 
onAdapterAttached()1018     private void onAdapterAttached() {
1019         if (mAreAccountsInitialized) {
1020             // No need to query again if we already did it.
1021             return;
1022         }
1023 
1024         mAdapter.queryRemoteConnectionServices(new RemoteServiceCallback.Stub() {
1025             @Override
1026             public void onResult(
1027                     final List<ComponentName> componentNames,
1028                     final List<IBinder> services) {
1029                 mHandler.post(new Runnable() {
1030                     @Override
1031                     public void run() {
1032                         for (int i = 0; i < componentNames.size() && i < services.size(); i++) {
1033                             mRemoteConnectionManager.addConnectionService(
1034                                     componentNames.get(i),
1035                                     IConnectionService.Stub.asInterface(services.get(i)));
1036                         }
1037                         onAccountsInitialized();
1038                         Log.d(this, "remote connection services found: " + services);
1039                     }
1040                 });
1041             }
1042 
1043             @Override
1044             public void onError() {
1045                 mHandler.post(new Runnable() {
1046                     @Override
1047                     public void run() {
1048                         mAreAccountsInitialized = true;
1049                     }
1050                 });
1051             }
1052         });
1053     }
1054 
1055     /**
1056      * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an
1057      * incoming request. This is used by {@code ConnectionService}s that are registered with
1058      * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} and want to be able to manage
1059      * SIM-based incoming calls.
1060      *
1061      * @param connectionManagerPhoneAccount See description at
1062      *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
1063      * @param request Details about the incoming call.
1064      * @return The {@code Connection} object to satisfy this call, or {@code null} to
1065      *         not handle the call.
1066      */
createRemoteIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1067     public final RemoteConnection createRemoteIncomingConnection(
1068             PhoneAccountHandle connectionManagerPhoneAccount,
1069             ConnectionRequest request) {
1070         return mRemoteConnectionManager.createRemoteConnection(
1071                 connectionManagerPhoneAccount, request, true);
1072     }
1073 
1074     /**
1075      * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an
1076      * outgoing request. This is used by {@code ConnectionService}s that are registered with
1077      * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} and want to be able to use the
1078      * SIM-based {@code ConnectionService} to place its outgoing calls.
1079      *
1080      * @param connectionManagerPhoneAccount See description at
1081      *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
1082      * @param request Details about the incoming call.
1083      * @return The {@code Connection} object to satisfy this call, or {@code null} to
1084      *         not handle the call.
1085      */
createRemoteOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1086     public final RemoteConnection createRemoteOutgoingConnection(
1087             PhoneAccountHandle connectionManagerPhoneAccount,
1088             ConnectionRequest request) {
1089         return mRemoteConnectionManager.createRemoteConnection(
1090                 connectionManagerPhoneAccount, request, false);
1091     }
1092 
1093     /**
1094      * Indicates to the relevant {@code RemoteConnectionService} that the specified
1095      * {@link RemoteConnection}s should be merged into a conference call.
1096      * <p>
1097      * If the conference request is successful, the method {@link #onRemoteConferenceAdded} will
1098      * be invoked.
1099      *
1100      * @param remoteConnection1 The first of the remote connections to conference.
1101      * @param remoteConnection2 The second of the remote connections to conference.
1102      */
conferenceRemoteConnections( RemoteConnection remoteConnection1, RemoteConnection remoteConnection2)1103     public final void conferenceRemoteConnections(
1104             RemoteConnection remoteConnection1,
1105             RemoteConnection remoteConnection2) {
1106         mRemoteConnectionManager.conferenceRemoteConnections(remoteConnection1, remoteConnection2);
1107     }
1108 
1109     /**
1110      * Adds a new conference call. When a conference call is created either as a result of an
1111      * explicit request via {@link #onConference} or otherwise, the connection service should supply
1112      * an instance of {@link Conference} by invoking this method. A conference call provided by this
1113      * method will persist until {@link Conference#destroy} is invoked on the conference instance.
1114      *
1115      * @param conference The new conference object.
1116      */
addConference(Conference conference)1117     public final void addConference(Conference conference) {
1118         Log.d(this, "addConference: conference=%s", conference);
1119 
1120         String id = addConferenceInternal(conference);
1121         if (id != null) {
1122             List<String> connectionIds = new ArrayList<>(2);
1123             for (Connection connection : conference.getConnections()) {
1124                 if (mIdByConnection.containsKey(connection)) {
1125                     connectionIds.add(mIdByConnection.get(connection));
1126                 }
1127             }
1128             conference.setTelecomCallId(id);
1129             ParcelableConference parcelableConference = new ParcelableConference(
1130                     conference.getPhoneAccountHandle(),
1131                     conference.getState(),
1132                     conference.getConnectionCapabilities(),
1133                     conference.getConnectionProperties(),
1134                     connectionIds,
1135                     conference.getVideoProvider() == null ?
1136                             null : conference.getVideoProvider().getInterface(),
1137                     conference.getVideoState(),
1138                     conference.getConnectTimeMillis(),
1139                     conference.getStatusHints(),
1140                     conference.getExtras());
1141 
1142             mAdapter.addConferenceCall(id, parcelableConference);
1143             mAdapter.setVideoProvider(id, conference.getVideoProvider());
1144             mAdapter.setVideoState(id, conference.getVideoState());
1145 
1146             // Go through any child calls and set the parent.
1147             for (Connection connection : conference.getConnections()) {
1148                 String connectionId = mIdByConnection.get(connection);
1149                 if (connectionId != null) {
1150                     mAdapter.setIsConferenced(connectionId, id);
1151                 }
1152             }
1153         }
1154     }
1155 
1156     /**
1157      * Adds a connection created by the {@link ConnectionService} and informs telecom of the new
1158      * connection.
1159      *
1160      * @param phoneAccountHandle The phone account handle for the connection.
1161      * @param connection The connection to add.
1162      */
addExistingConnection(PhoneAccountHandle phoneAccountHandle, Connection connection)1163     public final void addExistingConnection(PhoneAccountHandle phoneAccountHandle,
1164             Connection connection) {
1165 
1166         String id = addExistingConnectionInternal(phoneAccountHandle, connection);
1167         if (id != null) {
1168             List<String> emptyList = new ArrayList<>(0);
1169 
1170             ParcelableConnection parcelableConnection = new ParcelableConnection(
1171                     phoneAccountHandle,
1172                     connection.getState(),
1173                     connection.getConnectionCapabilities(),
1174                     connection.getConnectionProperties(),
1175                     connection.getAddress(),
1176                     connection.getAddressPresentation(),
1177                     connection.getCallerDisplayName(),
1178                     connection.getCallerDisplayNamePresentation(),
1179                     connection.getVideoProvider() == null ?
1180                             null : connection.getVideoProvider().getInterface(),
1181                     connection.getVideoState(),
1182                     connection.isRingbackRequested(),
1183                     connection.getAudioModeIsVoip(),
1184                     connection.getConnectTimeMillis(),
1185                     connection.getStatusHints(),
1186                     connection.getDisconnectCause(),
1187                     emptyList,
1188                     connection.getExtras());
1189             mAdapter.addExistingConnection(id, parcelableConnection);
1190         }
1191     }
1192 
1193     /**
1194      * Returns all the active {@code Connection}s for which this {@code ConnectionService}
1195      * has taken responsibility.
1196      *
1197      * @return A collection of {@code Connection}s created by this {@code ConnectionService}.
1198      */
getAllConnections()1199     public final Collection<Connection> getAllConnections() {
1200         return mConnectionById.values();
1201     }
1202 
1203     /**
1204      * Returns all the active {@code Conference}s for which this {@code ConnectionService}
1205      * has taken responsibility.
1206      *
1207      * @return A collection of {@code Conference}s created by this {@code ConnectionService}.
1208      */
getAllConferences()1209     public final Collection<Conference> getAllConferences() {
1210         return mConferenceById.values();
1211     }
1212 
1213     /**
1214      * Create a {@code Connection} given an incoming request. This is used to attach to existing
1215      * incoming calls.
1216      *
1217      * @param connectionManagerPhoneAccount See description at
1218      *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
1219      * @param request Details about the incoming call.
1220      * @return The {@code Connection} object to satisfy this call, or {@code null} to
1221      *         not handle the call.
1222      */
onCreateIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1223     public Connection onCreateIncomingConnection(
1224             PhoneAccountHandle connectionManagerPhoneAccount,
1225             ConnectionRequest request) {
1226         return null;
1227     }
1228 
1229     /**
1230      * Trigger recalculate functinality for conference calls. This is used when a Telephony
1231      * Connection is part of a conference controller but is not yet added to Connection
1232      * Service and hence cannot be added to the conference call.
1233      *
1234      * @hide
1235      */
triggerConferenceRecalculate()1236     public void triggerConferenceRecalculate() {
1237     }
1238 
1239     /**
1240      * Create a {@code Connection} given an outgoing request. This is used to initiate new
1241      * outgoing calls.
1242      *
1243      * @param connectionManagerPhoneAccount The connection manager account to use for managing
1244      *         this call.
1245      *         <p>
1246      *         If this parameter is not {@code null}, it means that this {@code ConnectionService}
1247      *         has registered one or more {@code PhoneAccount}s having
1248      *         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}. This parameter will contain
1249      *         one of these {@code PhoneAccount}s, while the {@code request} will contain another
1250      *         (usually but not always distinct) {@code PhoneAccount} to be used for actually
1251      *         making the connection.
1252      *         <p>
1253      *         If this parameter is {@code null}, it means that this {@code ConnectionService} is
1254      *         being asked to make a direct connection. The
1255      *         {@link ConnectionRequest#getAccountHandle()} of parameter {@code request} will be
1256      *         a {@code PhoneAccount} registered by this {@code ConnectionService} to use for
1257      *         making the connection.
1258      * @param request Details about the outgoing call.
1259      * @return The {@code Connection} object to satisfy this call, or the result of an invocation
1260      *         of {@link Connection#createFailedConnection(DisconnectCause)} to not handle the call.
1261      */
onCreateOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1262     public Connection onCreateOutgoingConnection(
1263             PhoneAccountHandle connectionManagerPhoneAccount,
1264             ConnectionRequest request) {
1265         return null;
1266     }
1267 
1268     /**
1269      * Create a {@code Connection} for a new unknown call. An unknown call is a call originating
1270      * from the ConnectionService that was neither a user-initiated outgoing call, nor an incoming
1271      * call created using
1272      * {@code TelecomManager#addNewIncomingCall(PhoneAccountHandle, android.os.Bundle)}.
1273      *
1274      * @param connectionManagerPhoneAccount
1275      * @param request
1276      * @return
1277      *
1278      * @hide
1279      */
onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1280     public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount,
1281             ConnectionRequest request) {
1282        return null;
1283     }
1284 
1285     /**
1286      * Conference two specified connections. Invoked when the user has made a request to merge the
1287      * specified connections into a conference call. In response, the connection service should
1288      * create an instance of {@link Conference} and pass it into {@link #addConference}.
1289      *
1290      * @param connection1 A connection to merge into a conference call.
1291      * @param connection2 A connection to merge into a conference call.
1292      */
onConference(Connection connection1, Connection connection2)1293     public void onConference(Connection connection1, Connection connection2) {}
1294 
1295     /**
1296      * Indicates that a remote conference has been created for existing {@link RemoteConnection}s.
1297      * When this method is invoked, this {@link ConnectionService} should create its own
1298      * representation of the conference call and send it to telecom using {@link #addConference}.
1299      * <p>
1300      * This is only relevant to {@link ConnectionService}s which are registered with
1301      * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}.
1302      *
1303      * @param conference The remote conference call.
1304      */
onRemoteConferenceAdded(RemoteConference conference)1305     public void onRemoteConferenceAdded(RemoteConference conference) {}
1306 
1307     /**
1308      * Called when an existing connection is added remotely.
1309      * @param connection The existing connection which was added.
1310      */
onRemoteExistingConnectionAdded(RemoteConnection connection)1311     public void onRemoteExistingConnectionAdded(RemoteConnection connection) {}
1312 
1313     /**
1314      * @hide
1315      */
containsConference(Conference conference)1316     public boolean containsConference(Conference conference) {
1317         return mIdByConference.containsKey(conference);
1318     }
1319 
1320     /** {@hide} */
addRemoteConference(RemoteConference remoteConference)1321     void addRemoteConference(RemoteConference remoteConference) {
1322         onRemoteConferenceAdded(remoteConference);
1323     }
1324 
1325     /** {@hide} */
addRemoteExistingConnection(RemoteConnection remoteConnection)1326     void addRemoteExistingConnection(RemoteConnection remoteConnection) {
1327         onRemoteExistingConnectionAdded(remoteConnection);
1328     }
1329 
onAccountsInitialized()1330     private void onAccountsInitialized() {
1331         mAreAccountsInitialized = true;
1332         for (Runnable r : mPreInitializationConnectionRequests) {
1333             r.run();
1334         }
1335         mPreInitializationConnectionRequests.clear();
1336     }
1337 
1338     /**
1339      * Adds an existing connection to the list of connections, identified by a new call ID unique
1340      * to this connection service.
1341      *
1342      * @param connection The connection.
1343      * @return The ID of the connection (e.g. the call-id).
1344      */
addExistingConnectionInternal(PhoneAccountHandle handle, Connection connection)1345     private String addExistingConnectionInternal(PhoneAccountHandle handle, Connection connection) {
1346         String id;
1347         if (handle == null) {
1348             // If no phone account handle was provided, we cannot be sure the call ID is unique,
1349             // so just use a random UUID.
1350             id = UUID.randomUUID().toString();
1351         } else {
1352             // Phone account handle was provided, so use the ConnectionService class name as a
1353             // prefix for a unique incremental call ID.
1354             id = handle.getComponentName().getClassName() + "@" + getNextCallId();
1355         }
1356         addConnection(id, connection);
1357         return id;
1358     }
1359 
addConnection(String callId, Connection connection)1360     private void addConnection(String callId, Connection connection) {
1361         connection.setTelecomCallId(callId);
1362         mConnectionById.put(callId, connection);
1363         mIdByConnection.put(connection, callId);
1364         connection.addConnectionListener(mConnectionListener);
1365         connection.setConnectionService(this);
1366     }
1367 
1368     /** {@hide} */
removeConnection(Connection connection)1369     protected void removeConnection(Connection connection) {
1370         connection.unsetConnectionService(this);
1371         connection.removeConnectionListener(mConnectionListener);
1372         String id = mIdByConnection.get(connection);
1373         if (id != null) {
1374             mConnectionById.remove(id);
1375             mIdByConnection.remove(connection);
1376             mAdapter.removeCall(id);
1377         }
1378     }
1379 
addConferenceInternal(Conference conference)1380     private String addConferenceInternal(Conference conference) {
1381         if (mIdByConference.containsKey(conference)) {
1382             Log.w(this, "Re-adding an existing conference: %s.", conference);
1383         } else if (conference != null) {
1384             // Conferences do not (yet) have a PhoneAccountHandle associated with them, so we
1385             // cannot determine a ConnectionService class name to associate with the ID, so use
1386             // a unique UUID (for now).
1387             String id = UUID.randomUUID().toString();
1388             mConferenceById.put(id, conference);
1389             mIdByConference.put(conference, id);
1390             conference.addListener(mConferenceListener);
1391             return id;
1392         }
1393 
1394         return null;
1395     }
1396 
removeConference(Conference conference)1397     private void removeConference(Conference conference) {
1398         if (mIdByConference.containsKey(conference)) {
1399             conference.removeListener(mConferenceListener);
1400 
1401             String id = mIdByConference.get(conference);
1402             mConferenceById.remove(id);
1403             mIdByConference.remove(conference);
1404             mAdapter.removeCall(id);
1405         }
1406     }
1407 
findConnectionForAction(String callId, String action)1408     private Connection findConnectionForAction(String callId, String action) {
1409         if (mConnectionById.containsKey(callId)) {
1410             return mConnectionById.get(callId);
1411         }
1412         Log.w(this, "%s - Cannot find Connection %s", action, callId);
1413         return getNullConnection();
1414     }
1415 
getNullConnection()1416     static synchronized Connection getNullConnection() {
1417         if (sNullConnection == null) {
1418             sNullConnection = new Connection() {};
1419         }
1420         return sNullConnection;
1421     }
1422 
findConferenceForAction(String conferenceId, String action)1423     private Conference findConferenceForAction(String conferenceId, String action) {
1424         if (mConferenceById.containsKey(conferenceId)) {
1425             return mConferenceById.get(conferenceId);
1426         }
1427         Log.w(this, "%s - Cannot find conference %s", action, conferenceId);
1428         return getNullConference();
1429     }
1430 
createConnectionIdList(List<Connection> connections)1431     private List<String> createConnectionIdList(List<Connection> connections) {
1432         List<String> ids = new ArrayList<>();
1433         for (Connection c : connections) {
1434             if (mIdByConnection.containsKey(c)) {
1435                 ids.add(mIdByConnection.get(c));
1436             }
1437         }
1438         Collections.sort(ids);
1439         return ids;
1440     }
1441 
1442     /**
1443      * Builds a list of {@link Connection} and {@link Conference} IDs based on the list of
1444      * {@link Conferenceable}s passed in.
1445      *
1446      * @param conferenceables The {@link Conferenceable} connections and conferences.
1447      * @return List of string conference and call Ids.
1448      */
createIdList(List<Conferenceable> conferenceables)1449     private List<String> createIdList(List<Conferenceable> conferenceables) {
1450         List<String> ids = new ArrayList<>();
1451         for (Conferenceable c : conferenceables) {
1452             // Only allow Connection and Conference conferenceables.
1453             if (c instanceof Connection) {
1454                 Connection connection = (Connection) c;
1455                 if (mIdByConnection.containsKey(connection)) {
1456                     ids.add(mIdByConnection.get(connection));
1457                 }
1458             } else if (c instanceof Conference) {
1459                 Conference conference = (Conference) c;
1460                 if (mIdByConference.containsKey(conference)) {
1461                     ids.add(mIdByConference.get(conference));
1462                 }
1463             }
1464         }
1465         Collections.sort(ids);
1466         return ids;
1467     }
1468 
getNullConference()1469     private Conference getNullConference() {
1470         if (sNullConference == null) {
1471             sNullConference = new Conference(null) {};
1472         }
1473         return sNullConference;
1474     }
1475 
endAllConnections()1476     private void endAllConnections() {
1477         // Unbound from telecomm.  We should end all connections and conferences.
1478         for (Connection connection : mIdByConnection.keySet()) {
1479             // only operate on top-level calls. Conference calls will be removed on their own.
1480             if (connection.getConference() == null) {
1481                 connection.onDisconnect();
1482             }
1483         }
1484         for (Conference conference : mIdByConference.keySet()) {
1485             conference.onDisconnect();
1486         }
1487     }
1488 
1489     /**
1490      * Retrieves the next call ID as maintainted by the connection service.
1491      *
1492      * @return The call ID.
1493      */
getNextCallId()1494     private int getNextCallId() {
1495         synchronized(mIdSyncRoot) {
1496             return ++mId;
1497         }
1498     }
1499 }
1500