• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_PULLING_CALL:
560                     mAdapter.setPulling(id);
561                     break;
562                 case Connection.STATE_DISCONNECTED:
563                     // Handled in onDisconnected()
564                     break;
565                 case Connection.STATE_HOLDING:
566                     mAdapter.setOnHold(id);
567                     break;
568                 case Connection.STATE_NEW:
569                     // Nothing to tell Telecom
570                     break;
571                 case Connection.STATE_RINGING:
572                     mAdapter.setRinging(id);
573                     break;
574             }
575         }
576 
577         @Override
578         public void onDisconnected(Connection c, DisconnectCause disconnectCause) {
579             String id = mIdByConnection.get(c);
580             Log.d(this, "Adapter set disconnected %s", disconnectCause);
581             mAdapter.setDisconnected(id, disconnectCause);
582         }
583 
584         @Override
585         public void onVideoStateChanged(Connection c, int videoState) {
586             String id = mIdByConnection.get(c);
587             Log.d(this, "Adapter set video state %d", videoState);
588             mAdapter.setVideoState(id, videoState);
589         }
590 
591         @Override
592         public void onAddressChanged(Connection c, Uri address, int presentation) {
593             String id = mIdByConnection.get(c);
594             mAdapter.setAddress(id, address, presentation);
595         }
596 
597         @Override
598         public void onCallerDisplayNameChanged(
599                 Connection c, String callerDisplayName, int presentation) {
600             String id = mIdByConnection.get(c);
601             mAdapter.setCallerDisplayName(id, callerDisplayName, presentation);
602         }
603 
604         @Override
605         public void onDestroyed(Connection c) {
606             removeConnection(c);
607         }
608 
609         @Override
610         public void onPostDialWait(Connection c, String remaining) {
611             String id = mIdByConnection.get(c);
612             Log.d(this, "Adapter onPostDialWait %s, %s", c, remaining);
613             mAdapter.onPostDialWait(id, remaining);
614         }
615 
616         @Override
617         public void onPostDialChar(Connection c, char nextChar) {
618             String id = mIdByConnection.get(c);
619             Log.d(this, "Adapter onPostDialChar %s, %s", c, nextChar);
620             mAdapter.onPostDialChar(id, nextChar);
621         }
622 
623         @Override
624         public void onRingbackRequested(Connection c, boolean ringback) {
625             String id = mIdByConnection.get(c);
626             Log.d(this, "Adapter onRingback %b", ringback);
627             mAdapter.setRingbackRequested(id, ringback);
628         }
629 
630         @Override
631         public void onConnectionCapabilitiesChanged(Connection c, int capabilities) {
632             String id = mIdByConnection.get(c);
633             Log.d(this, "capabilities: parcelableconnection: %s",
634                     Connection.capabilitiesToString(capabilities));
635             mAdapter.setConnectionCapabilities(id, capabilities);
636         }
637 
638         @Override
639         public void onConnectionPropertiesChanged(Connection c, int properties) {
640             String id = mIdByConnection.get(c);
641             Log.d(this, "properties: parcelableconnection: %s",
642                     Connection.propertiesToString(properties));
643             mAdapter.setConnectionProperties(id, properties);
644         }
645 
646         @Override
647         public void onVideoProviderChanged(Connection c, Connection.VideoProvider videoProvider) {
648             String id = mIdByConnection.get(c);
649             Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c,
650                     videoProvider);
651             mAdapter.setVideoProvider(id, videoProvider);
652         }
653 
654         @Override
655         public void onAudioModeIsVoipChanged(Connection c, boolean isVoip) {
656             String id = mIdByConnection.get(c);
657             mAdapter.setIsVoipAudioMode(id, isVoip);
658         }
659 
660         @Override
661         public void onStatusHintsChanged(Connection c, StatusHints statusHints) {
662             String id = mIdByConnection.get(c);
663             mAdapter.setStatusHints(id, statusHints);
664         }
665 
666         @Override
667         public void onConferenceablesChanged(
668                 Connection connection, List<Conferenceable> conferenceables) {
669             mAdapter.setConferenceableConnections(
670                     mIdByConnection.get(connection),
671                     createIdList(conferenceables));
672         }
673 
674         @Override
675         public void onConferenceChanged(Connection connection, Conference conference) {
676             String id = mIdByConnection.get(connection);
677             if (id != null) {
678                 String conferenceId = null;
679                 if (conference != null) {
680                     conferenceId = mIdByConference.get(conference);
681                 }
682                 mAdapter.setIsConferenced(id, conferenceId);
683             }
684         }
685 
686         @Override
687         public void onConferenceMergeFailed(Connection connection) {
688             String id = mIdByConnection.get(connection);
689             if (id != null) {
690                 mAdapter.onConferenceMergeFailed(id);
691             }
692         }
693 
694         @Override
695         public void onExtrasChanged(Connection c, Bundle extras) {
696             String id = mIdByConnection.get(c);
697             if (id != null) {
698                 mAdapter.putExtras(id, extras);
699             }
700         }
701 
702         public void onExtrasRemoved(Connection c, List<String> keys) {
703             String id = mIdByConnection.get(c);
704             if (id != null) {
705                 mAdapter.removeExtras(id, keys);
706             }
707         }
708 
709 
710         @Override
711         public void onConnectionEvent(Connection connection, String event, Bundle extras) {
712             String id = mIdByConnection.get(connection);
713             if (id != null) {
714                 mAdapter.onConnectionEvent(id, event, extras);
715             }
716         }
717     };
718 
719     /** {@inheritDoc} */
720     @Override
onBind(Intent intent)721     public final IBinder onBind(Intent intent) {
722         return mBinder;
723     }
724 
725     /** {@inheritDoc} */
726     @Override
onUnbind(Intent intent)727     public boolean onUnbind(Intent intent) {
728         endAllConnections();
729         return super.onUnbind(intent);
730     }
731 
732     /**
733      * This can be used by telecom to either create a new outgoing call or attach to an existing
734      * incoming call. In either case, telecom will cycle through a set of services and call
735      * createConnection util a connection service cancels the process or completes it successfully.
736      */
createConnection( final PhoneAccountHandle callManagerAccount, final String callId, final ConnectionRequest request, boolean isIncoming, boolean isUnknown)737     private void createConnection(
738             final PhoneAccountHandle callManagerAccount,
739             final String callId,
740             final ConnectionRequest request,
741             boolean isIncoming,
742             boolean isUnknown) {
743         Log.d(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " +
744                         "isIncoming: %b, isUnknown: %b", callManagerAccount, callId, request,
745                 isIncoming,
746                 isUnknown);
747 
748         Connection connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request)
749                 : isIncoming ? onCreateIncomingConnection(callManagerAccount, request)
750                 : onCreateOutgoingConnection(callManagerAccount, request);
751         Log.d(this, "createConnection, connection: %s", connection);
752         if (connection == null) {
753             connection = Connection.createFailedConnection(
754                     new DisconnectCause(DisconnectCause.ERROR));
755         }
756 
757         connection.setTelecomCallId(callId);
758         if (connection.getState() != Connection.STATE_DISCONNECTED) {
759             addConnection(callId, connection);
760         }
761 
762         Uri address = connection.getAddress();
763         String number = address == null ? "null" : address.getSchemeSpecificPart();
764         Log.v(this, "createConnection, number: %s, state: %s, capabilities: %s, properties: %s",
765                 Connection.toLogSafePhoneNumber(number),
766                 Connection.stateToString(connection.getState()),
767                 Connection.capabilitiesToString(connection.getConnectionCapabilities()),
768                 Connection.propertiesToString(connection.getConnectionProperties()));
769 
770         Log.d(this, "createConnection, calling handleCreateConnectionSuccessful %s", callId);
771         mAdapter.handleCreateConnectionComplete(
772                 callId,
773                 request,
774                 new ParcelableConnection(
775                         request.getAccountHandle(),
776                         connection.getState(),
777                         connection.getConnectionCapabilities(),
778                         connection.getConnectionProperties(),
779                         connection.getAddress(),
780                         connection.getAddressPresentation(),
781                         connection.getCallerDisplayName(),
782                         connection.getCallerDisplayNamePresentation(),
783                         connection.getVideoProvider() == null ?
784                                 null : connection.getVideoProvider().getInterface(),
785                         connection.getVideoState(),
786                         connection.isRingbackRequested(),
787                         connection.getAudioModeIsVoip(),
788                         connection.getConnectTimeMillis(),
789                         connection.getStatusHints(),
790                         connection.getDisconnectCause(),
791                         createIdList(connection.getConferenceables()),
792                         connection.getExtras()));
793         if (isUnknown) {
794             triggerConferenceRecalculate();
795         }
796     }
797 
abort(String callId)798     private void abort(String callId) {
799         Log.d(this, "abort %s", callId);
800         findConnectionForAction(callId, "abort").onAbort();
801     }
802 
answerVideo(String callId, int videoState)803     private void answerVideo(String callId, int videoState) {
804         Log.d(this, "answerVideo %s", callId);
805         findConnectionForAction(callId, "answer").onAnswer(videoState);
806     }
807 
answer(String callId)808     private void answer(String callId) {
809         Log.d(this, "answer %s", callId);
810         findConnectionForAction(callId, "answer").onAnswer();
811     }
812 
reject(String callId)813     private void reject(String callId) {
814         Log.d(this, "reject %s", callId);
815         findConnectionForAction(callId, "reject").onReject();
816     }
817 
reject(String callId, String rejectWithMessage)818     private void reject(String callId, String rejectWithMessage) {
819         Log.d(this, "reject %s with message", callId);
820         findConnectionForAction(callId, "reject").onReject(rejectWithMessage);
821     }
822 
silence(String callId)823     private void silence(String callId) {
824         Log.d(this, "silence %s", callId);
825         findConnectionForAction(callId, "silence").onSilence();
826     }
827 
disconnect(String callId)828     private void disconnect(String callId) {
829         Log.d(this, "disconnect %s", callId);
830         if (mConnectionById.containsKey(callId)) {
831             findConnectionForAction(callId, "disconnect").onDisconnect();
832         } else {
833             findConferenceForAction(callId, "disconnect").onDisconnect();
834         }
835     }
836 
hold(String callId)837     private void hold(String callId) {
838         Log.d(this, "hold %s", callId);
839         if (mConnectionById.containsKey(callId)) {
840             findConnectionForAction(callId, "hold").onHold();
841         } else {
842             findConferenceForAction(callId, "hold").onHold();
843         }
844     }
845 
unhold(String callId)846     private void unhold(String callId) {
847         Log.d(this, "unhold %s", callId);
848         if (mConnectionById.containsKey(callId)) {
849             findConnectionForAction(callId, "unhold").onUnhold();
850         } else {
851             findConferenceForAction(callId, "unhold").onUnhold();
852         }
853     }
854 
onCallAudioStateChanged(String callId, CallAudioState callAudioState)855     private void onCallAudioStateChanged(String callId, CallAudioState callAudioState) {
856         Log.d(this, "onAudioStateChanged %s %s", callId, callAudioState);
857         if (mConnectionById.containsKey(callId)) {
858             findConnectionForAction(callId, "onCallAudioStateChanged").setCallAudioState(
859                     callAudioState);
860         } else {
861             findConferenceForAction(callId, "onCallAudioStateChanged").setCallAudioState(
862                     callAudioState);
863         }
864     }
865 
playDtmfTone(String callId, char digit)866     private void playDtmfTone(String callId, char digit) {
867         Log.d(this, "playDtmfTone %s %c", callId, digit);
868         if (mConnectionById.containsKey(callId)) {
869             findConnectionForAction(callId, "playDtmfTone").onPlayDtmfTone(digit);
870         } else {
871             findConferenceForAction(callId, "playDtmfTone").onPlayDtmfTone(digit);
872         }
873     }
874 
stopDtmfTone(String callId)875     private void stopDtmfTone(String callId) {
876         Log.d(this, "stopDtmfTone %s", callId);
877         if (mConnectionById.containsKey(callId)) {
878             findConnectionForAction(callId, "stopDtmfTone").onStopDtmfTone();
879         } else {
880             findConferenceForAction(callId, "stopDtmfTone").onStopDtmfTone();
881         }
882     }
883 
conference(String callId1, String callId2)884     private void conference(String callId1, String callId2) {
885         Log.d(this, "conference %s, %s", callId1, callId2);
886 
887         // Attempt to get second connection or conference.
888         Connection connection2 = findConnectionForAction(callId2, "conference");
889         Conference conference2 = getNullConference();
890         if (connection2 == getNullConnection()) {
891             conference2 = findConferenceForAction(callId2, "conference");
892             if (conference2 == getNullConference()) {
893                 Log.w(this, "Connection2 or Conference2 missing in conference request %s.",
894                         callId2);
895                 return;
896             }
897         }
898 
899         // Attempt to get first connection or conference and perform merge.
900         Connection connection1 = findConnectionForAction(callId1, "conference");
901         if (connection1 == getNullConnection()) {
902             Conference conference1 = findConferenceForAction(callId1, "addConnection");
903             if (conference1 == getNullConference()) {
904                 Log.w(this,
905                         "Connection1 or Conference1 missing in conference request %s.",
906                         callId1);
907             } else {
908                 // Call 1 is a conference.
909                 if (connection2 != getNullConnection()) {
910                     // Call 2 is a connection so merge via call 1 (conference).
911                     conference1.onMerge(connection2);
912                 } else {
913                     // Call 2 is ALSO a conference; this should never happen.
914                     Log.wtf(this, "There can only be one conference and an attempt was made to " +
915                             "merge two conferences.");
916                     return;
917                 }
918             }
919         } else {
920             // Call 1 is a connection.
921             if (conference2 != getNullConference()) {
922                 // Call 2 is a conference, so merge via call 2.
923                 conference2.onMerge(connection1);
924             } else {
925                 // Call 2 is a connection, so merge together.
926                 onConference(connection1, connection2);
927             }
928         }
929     }
930 
splitFromConference(String callId)931     private void splitFromConference(String callId) {
932         Log.d(this, "splitFromConference(%s)", callId);
933 
934         Connection connection = findConnectionForAction(callId, "splitFromConference");
935         if (connection == getNullConnection()) {
936             Log.w(this, "Connection missing in conference request %s.", callId);
937             return;
938         }
939 
940         Conference conference = connection.getConference();
941         if (conference != null) {
942             conference.onSeparate(connection);
943         }
944     }
945 
mergeConference(String callId)946     private void mergeConference(String callId) {
947         Log.d(this, "mergeConference(%s)", callId);
948         Conference conference = findConferenceForAction(callId, "mergeConference");
949         if (conference != null) {
950             conference.onMerge();
951         }
952     }
953 
swapConference(String callId)954     private void swapConference(String callId) {
955         Log.d(this, "swapConference(%s)", callId);
956         Conference conference = findConferenceForAction(callId, "swapConference");
957         if (conference != null) {
958             conference.onSwap();
959         }
960     }
961 
962     /**
963      * Notifies a {@link Connection} of a request to pull an external call.
964      *
965      * See {@link Call#pullExternalCall()}.
966      *
967      * @param callId The ID of the call to pull.
968      */
pullExternalCall(String callId)969     private void pullExternalCall(String callId) {
970         Log.d(this, "pullExternalCall(%s)", callId);
971         Connection connection = findConnectionForAction(callId, "pullExternalCall");
972         if (connection != null) {
973             connection.onPullExternalCall();
974         }
975     }
976 
977     /**
978      * Notifies a {@link Connection} of a call event.
979      *
980      * See {@link Call#sendCallEvent(String, Bundle)}.
981      *
982      * @param callId The ID of the call receiving the event.
983      * @param event The event.
984      * @param extras Extras associated with the event.
985      */
sendCallEvent(String callId, String event, Bundle extras)986     private void sendCallEvent(String callId, String event, Bundle extras) {
987         Log.d(this, "sendCallEvent(%s, %s)", callId, event);
988         Connection connection = findConnectionForAction(callId, "sendCallEvent");
989         if (connection != null) {
990             connection.onCallEvent(event, extras);
991         }
992 
993     }
994 
995     /**
996      * Notifies a {@link Connection} or {@link Conference} of a change to the extras from Telecom.
997      * <p>
998      * These extra changes can originate from Telecom itself, or from an {@link InCallService} via
999      * the {@link android.telecom.Call#putExtra(String, boolean)},
1000      * {@link android.telecom.Call#putExtra(String, int)},
1001      * {@link android.telecom.Call#putExtra(String, String)},
1002      * {@link Call#removeExtras(List)}.
1003      *
1004      * @param callId The ID of the call receiving the event.
1005      * @param extras The new extras bundle.
1006      */
handleExtrasChanged(String callId, Bundle extras)1007     private void handleExtrasChanged(String callId, Bundle extras) {
1008         Log.d(this, "handleExtrasChanged(%s, %s)", callId, extras);
1009         if (mConnectionById.containsKey(callId)) {
1010             findConnectionForAction(callId, "handleExtrasChanged").handleExtrasChanged(extras);
1011         } else if (mConferenceById.containsKey(callId)) {
1012             findConferenceForAction(callId, "handleExtrasChanged").handleExtrasChanged(extras);
1013         }
1014     }
1015 
onPostDialContinue(String callId, boolean proceed)1016     private void onPostDialContinue(String callId, boolean proceed) {
1017         Log.d(this, "onPostDialContinue(%s)", callId);
1018         findConnectionForAction(callId, "stopDtmfTone").onPostDialContinue(proceed);
1019     }
1020 
onAdapterAttached()1021     private void onAdapterAttached() {
1022         if (mAreAccountsInitialized) {
1023             // No need to query again if we already did it.
1024             return;
1025         }
1026 
1027         mAdapter.queryRemoteConnectionServices(new RemoteServiceCallback.Stub() {
1028             @Override
1029             public void onResult(
1030                     final List<ComponentName> componentNames,
1031                     final List<IBinder> services) {
1032                 mHandler.post(new Runnable() {
1033                     @Override
1034                     public void run() {
1035                         for (int i = 0; i < componentNames.size() && i < services.size(); i++) {
1036                             mRemoteConnectionManager.addConnectionService(
1037                                     componentNames.get(i),
1038                                     IConnectionService.Stub.asInterface(services.get(i)));
1039                         }
1040                         onAccountsInitialized();
1041                         Log.d(this, "remote connection services found: " + services);
1042                     }
1043                 });
1044             }
1045 
1046             @Override
1047             public void onError() {
1048                 mHandler.post(new Runnable() {
1049                     @Override
1050                     public void run() {
1051                         mAreAccountsInitialized = true;
1052                     }
1053                 });
1054             }
1055         });
1056     }
1057 
1058     /**
1059      * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an
1060      * incoming request. This is used by {@code ConnectionService}s that are registered with
1061      * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} and want to be able to manage
1062      * SIM-based incoming calls.
1063      *
1064      * @param connectionManagerPhoneAccount See description at
1065      *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
1066      * @param request Details about the incoming call.
1067      * @return The {@code Connection} object to satisfy this call, or {@code null} to
1068      *         not handle the call.
1069      */
createRemoteIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1070     public final RemoteConnection createRemoteIncomingConnection(
1071             PhoneAccountHandle connectionManagerPhoneAccount,
1072             ConnectionRequest request) {
1073         return mRemoteConnectionManager.createRemoteConnection(
1074                 connectionManagerPhoneAccount, request, true);
1075     }
1076 
1077     /**
1078      * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an
1079      * outgoing request. This is used by {@code ConnectionService}s that are registered with
1080      * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} and want to be able to use the
1081      * SIM-based {@code ConnectionService} to place its outgoing calls.
1082      *
1083      * @param connectionManagerPhoneAccount See description at
1084      *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
1085      * @param request Details about the incoming call.
1086      * @return The {@code Connection} object to satisfy this call, or {@code null} to
1087      *         not handle the call.
1088      */
createRemoteOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1089     public final RemoteConnection createRemoteOutgoingConnection(
1090             PhoneAccountHandle connectionManagerPhoneAccount,
1091             ConnectionRequest request) {
1092         return mRemoteConnectionManager.createRemoteConnection(
1093                 connectionManagerPhoneAccount, request, false);
1094     }
1095 
1096     /**
1097      * Indicates to the relevant {@code RemoteConnectionService} that the specified
1098      * {@link RemoteConnection}s should be merged into a conference call.
1099      * <p>
1100      * If the conference request is successful, the method {@link #onRemoteConferenceAdded} will
1101      * be invoked.
1102      *
1103      * @param remoteConnection1 The first of the remote connections to conference.
1104      * @param remoteConnection2 The second of the remote connections to conference.
1105      */
conferenceRemoteConnections( RemoteConnection remoteConnection1, RemoteConnection remoteConnection2)1106     public final void conferenceRemoteConnections(
1107             RemoteConnection remoteConnection1,
1108             RemoteConnection remoteConnection2) {
1109         mRemoteConnectionManager.conferenceRemoteConnections(remoteConnection1, remoteConnection2);
1110     }
1111 
1112     /**
1113      * Adds a new conference call. When a conference call is created either as a result of an
1114      * explicit request via {@link #onConference} or otherwise, the connection service should supply
1115      * an instance of {@link Conference} by invoking this method. A conference call provided by this
1116      * method will persist until {@link Conference#destroy} is invoked on the conference instance.
1117      *
1118      * @param conference The new conference object.
1119      */
addConference(Conference conference)1120     public final void addConference(Conference conference) {
1121         Log.d(this, "addConference: conference=%s", conference);
1122 
1123         String id = addConferenceInternal(conference);
1124         if (id != null) {
1125             List<String> connectionIds = new ArrayList<>(2);
1126             for (Connection connection : conference.getConnections()) {
1127                 if (mIdByConnection.containsKey(connection)) {
1128                     connectionIds.add(mIdByConnection.get(connection));
1129                 }
1130             }
1131             conference.setTelecomCallId(id);
1132             ParcelableConference parcelableConference = new ParcelableConference(
1133                     conference.getPhoneAccountHandle(),
1134                     conference.getState(),
1135                     conference.getConnectionCapabilities(),
1136                     conference.getConnectionProperties(),
1137                     connectionIds,
1138                     conference.getVideoProvider() == null ?
1139                             null : conference.getVideoProvider().getInterface(),
1140                     conference.getVideoState(),
1141                     conference.getConnectTimeMillis(),
1142                     conference.getStatusHints(),
1143                     conference.getExtras());
1144 
1145             mAdapter.addConferenceCall(id, parcelableConference);
1146             mAdapter.setVideoProvider(id, conference.getVideoProvider());
1147             mAdapter.setVideoState(id, conference.getVideoState());
1148 
1149             // Go through any child calls and set the parent.
1150             for (Connection connection : conference.getConnections()) {
1151                 String connectionId = mIdByConnection.get(connection);
1152                 if (connectionId != null) {
1153                     mAdapter.setIsConferenced(connectionId, id);
1154                 }
1155             }
1156         }
1157     }
1158 
1159     /**
1160      * Adds a connection created by the {@link ConnectionService} and informs telecom of the new
1161      * connection.
1162      *
1163      * @param phoneAccountHandle The phone account handle for the connection.
1164      * @param connection The connection to add.
1165      */
addExistingConnection(PhoneAccountHandle phoneAccountHandle, Connection connection)1166     public final void addExistingConnection(PhoneAccountHandle phoneAccountHandle,
1167             Connection connection) {
1168 
1169         String id = addExistingConnectionInternal(phoneAccountHandle, connection);
1170         if (id != null) {
1171             List<String> emptyList = new ArrayList<>(0);
1172 
1173             ParcelableConnection parcelableConnection = new ParcelableConnection(
1174                     phoneAccountHandle,
1175                     connection.getState(),
1176                     connection.getConnectionCapabilities(),
1177                     connection.getConnectionProperties(),
1178                     connection.getAddress(),
1179                     connection.getAddressPresentation(),
1180                     connection.getCallerDisplayName(),
1181                     connection.getCallerDisplayNamePresentation(),
1182                     connection.getVideoProvider() == null ?
1183                             null : connection.getVideoProvider().getInterface(),
1184                     connection.getVideoState(),
1185                     connection.isRingbackRequested(),
1186                     connection.getAudioModeIsVoip(),
1187                     connection.getConnectTimeMillis(),
1188                     connection.getStatusHints(),
1189                     connection.getDisconnectCause(),
1190                     emptyList,
1191                     connection.getExtras());
1192             mAdapter.addExistingConnection(id, parcelableConnection);
1193         }
1194     }
1195 
1196     /**
1197      * Returns all the active {@code Connection}s for which this {@code ConnectionService}
1198      * has taken responsibility.
1199      *
1200      * @return A collection of {@code Connection}s created by this {@code ConnectionService}.
1201      */
getAllConnections()1202     public final Collection<Connection> getAllConnections() {
1203         return mConnectionById.values();
1204     }
1205 
1206     /**
1207      * Returns all the active {@code Conference}s for which this {@code ConnectionService}
1208      * has taken responsibility.
1209      *
1210      * @return A collection of {@code Conference}s created by this {@code ConnectionService}.
1211      */
getAllConferences()1212     public final Collection<Conference> getAllConferences() {
1213         return mConferenceById.values();
1214     }
1215 
1216     /**
1217      * Create a {@code Connection} given an incoming request. This is used to attach to existing
1218      * incoming calls.
1219      *
1220      * @param connectionManagerPhoneAccount See description at
1221      *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
1222      * @param request Details about the incoming call.
1223      * @return The {@code Connection} object to satisfy this call, or {@code null} to
1224      *         not handle the call.
1225      */
onCreateIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1226     public Connection onCreateIncomingConnection(
1227             PhoneAccountHandle connectionManagerPhoneAccount,
1228             ConnectionRequest request) {
1229         return null;
1230     }
1231 
1232     /**
1233      * Trigger recalculate functinality for conference calls. This is used when a Telephony
1234      * Connection is part of a conference controller but is not yet added to Connection
1235      * Service and hence cannot be added to the conference call.
1236      *
1237      * @hide
1238      */
triggerConferenceRecalculate()1239     public void triggerConferenceRecalculate() {
1240     }
1241 
1242     /**
1243      * Create a {@code Connection} given an outgoing request. This is used to initiate new
1244      * outgoing calls.
1245      *
1246      * @param connectionManagerPhoneAccount The connection manager account to use for managing
1247      *         this call.
1248      *         <p>
1249      *         If this parameter is not {@code null}, it means that this {@code ConnectionService}
1250      *         has registered one or more {@code PhoneAccount}s having
1251      *         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}. This parameter will contain
1252      *         one of these {@code PhoneAccount}s, while the {@code request} will contain another
1253      *         (usually but not always distinct) {@code PhoneAccount} to be used for actually
1254      *         making the connection.
1255      *         <p>
1256      *         If this parameter is {@code null}, it means that this {@code ConnectionService} is
1257      *         being asked to make a direct connection. The
1258      *         {@link ConnectionRequest#getAccountHandle()} of parameter {@code request} will be
1259      *         a {@code PhoneAccount} registered by this {@code ConnectionService} to use for
1260      *         making the connection.
1261      * @param request Details about the outgoing call.
1262      * @return The {@code Connection} object to satisfy this call, or the result of an invocation
1263      *         of {@link Connection#createFailedConnection(DisconnectCause)} to not handle the call.
1264      */
onCreateOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1265     public Connection onCreateOutgoingConnection(
1266             PhoneAccountHandle connectionManagerPhoneAccount,
1267             ConnectionRequest request) {
1268         return null;
1269     }
1270 
1271     /**
1272      * Create a {@code Connection} for a new unknown call. An unknown call is a call originating
1273      * from the ConnectionService that was neither a user-initiated outgoing call, nor an incoming
1274      * call created using
1275      * {@code TelecomManager#addNewIncomingCall(PhoneAccountHandle, android.os.Bundle)}.
1276      *
1277      * @param connectionManagerPhoneAccount
1278      * @param request
1279      * @return
1280      *
1281      * @hide
1282      */
onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1283     public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount,
1284             ConnectionRequest request) {
1285        return null;
1286     }
1287 
1288     /**
1289      * Conference two specified connections. Invoked when the user has made a request to merge the
1290      * specified connections into a conference call. In response, the connection service should
1291      * create an instance of {@link Conference} and pass it into {@link #addConference}.
1292      *
1293      * @param connection1 A connection to merge into a conference call.
1294      * @param connection2 A connection to merge into a conference call.
1295      */
onConference(Connection connection1, Connection connection2)1296     public void onConference(Connection connection1, Connection connection2) {}
1297 
1298     /**
1299      * Indicates that a remote conference has been created for existing {@link RemoteConnection}s.
1300      * When this method is invoked, this {@link ConnectionService} should create its own
1301      * representation of the conference call and send it to telecom using {@link #addConference}.
1302      * <p>
1303      * This is only relevant to {@link ConnectionService}s which are registered with
1304      * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}.
1305      *
1306      * @param conference The remote conference call.
1307      */
onRemoteConferenceAdded(RemoteConference conference)1308     public void onRemoteConferenceAdded(RemoteConference conference) {}
1309 
1310     /**
1311      * Called when an existing connection is added remotely.
1312      * @param connection The existing connection which was added.
1313      */
onRemoteExistingConnectionAdded(RemoteConnection connection)1314     public void onRemoteExistingConnectionAdded(RemoteConnection connection) {}
1315 
1316     /**
1317      * @hide
1318      */
containsConference(Conference conference)1319     public boolean containsConference(Conference conference) {
1320         return mIdByConference.containsKey(conference);
1321     }
1322 
1323     /** {@hide} */
addRemoteConference(RemoteConference remoteConference)1324     void addRemoteConference(RemoteConference remoteConference) {
1325         onRemoteConferenceAdded(remoteConference);
1326     }
1327 
1328     /** {@hide} */
addRemoteExistingConnection(RemoteConnection remoteConnection)1329     void addRemoteExistingConnection(RemoteConnection remoteConnection) {
1330         onRemoteExistingConnectionAdded(remoteConnection);
1331     }
1332 
onAccountsInitialized()1333     private void onAccountsInitialized() {
1334         mAreAccountsInitialized = true;
1335         for (Runnable r : mPreInitializationConnectionRequests) {
1336             r.run();
1337         }
1338         mPreInitializationConnectionRequests.clear();
1339     }
1340 
1341     /**
1342      * Adds an existing connection to the list of connections, identified by a new call ID unique
1343      * to this connection service.
1344      *
1345      * @param connection The connection.
1346      * @return The ID of the connection (e.g. the call-id).
1347      */
addExistingConnectionInternal(PhoneAccountHandle handle, Connection connection)1348     private String addExistingConnectionInternal(PhoneAccountHandle handle, Connection connection) {
1349         String id;
1350         if (handle == null) {
1351             // If no phone account handle was provided, we cannot be sure the call ID is unique,
1352             // so just use a random UUID.
1353             id = UUID.randomUUID().toString();
1354         } else {
1355             // Phone account handle was provided, so use the ConnectionService class name as a
1356             // prefix for a unique incremental call ID.
1357             id = handle.getComponentName().getClassName() + "@" + getNextCallId();
1358         }
1359         addConnection(id, connection);
1360         return id;
1361     }
1362 
addConnection(String callId, Connection connection)1363     private void addConnection(String callId, Connection connection) {
1364         connection.setTelecomCallId(callId);
1365         mConnectionById.put(callId, connection);
1366         mIdByConnection.put(connection, callId);
1367         connection.addConnectionListener(mConnectionListener);
1368         connection.setConnectionService(this);
1369     }
1370 
1371     /** {@hide} */
removeConnection(Connection connection)1372     protected void removeConnection(Connection connection) {
1373         connection.unsetConnectionService(this);
1374         connection.removeConnectionListener(mConnectionListener);
1375         String id = mIdByConnection.get(connection);
1376         if (id != null) {
1377             mConnectionById.remove(id);
1378             mIdByConnection.remove(connection);
1379             mAdapter.removeCall(id);
1380         }
1381     }
1382 
addConferenceInternal(Conference conference)1383     private String addConferenceInternal(Conference conference) {
1384         if (mIdByConference.containsKey(conference)) {
1385             Log.w(this, "Re-adding an existing conference: %s.", conference);
1386         } else if (conference != null) {
1387             // Conferences do not (yet) have a PhoneAccountHandle associated with them, so we
1388             // cannot determine a ConnectionService class name to associate with the ID, so use
1389             // a unique UUID (for now).
1390             String id = UUID.randomUUID().toString();
1391             mConferenceById.put(id, conference);
1392             mIdByConference.put(conference, id);
1393             conference.addListener(mConferenceListener);
1394             return id;
1395         }
1396 
1397         return null;
1398     }
1399 
removeConference(Conference conference)1400     private void removeConference(Conference conference) {
1401         if (mIdByConference.containsKey(conference)) {
1402             conference.removeListener(mConferenceListener);
1403 
1404             String id = mIdByConference.get(conference);
1405             mConferenceById.remove(id);
1406             mIdByConference.remove(conference);
1407             mAdapter.removeCall(id);
1408         }
1409     }
1410 
findConnectionForAction(String callId, String action)1411     private Connection findConnectionForAction(String callId, String action) {
1412         if (mConnectionById.containsKey(callId)) {
1413             return mConnectionById.get(callId);
1414         }
1415         Log.w(this, "%s - Cannot find Connection %s", action, callId);
1416         return getNullConnection();
1417     }
1418 
getNullConnection()1419     static synchronized Connection getNullConnection() {
1420         if (sNullConnection == null) {
1421             sNullConnection = new Connection() {};
1422         }
1423         return sNullConnection;
1424     }
1425 
findConferenceForAction(String conferenceId, String action)1426     private Conference findConferenceForAction(String conferenceId, String action) {
1427         if (mConferenceById.containsKey(conferenceId)) {
1428             return mConferenceById.get(conferenceId);
1429         }
1430         Log.w(this, "%s - Cannot find conference %s", action, conferenceId);
1431         return getNullConference();
1432     }
1433 
createConnectionIdList(List<Connection> connections)1434     private List<String> createConnectionIdList(List<Connection> connections) {
1435         List<String> ids = new ArrayList<>();
1436         for (Connection c : connections) {
1437             if (mIdByConnection.containsKey(c)) {
1438                 ids.add(mIdByConnection.get(c));
1439             }
1440         }
1441         Collections.sort(ids);
1442         return ids;
1443     }
1444 
1445     /**
1446      * Builds a list of {@link Connection} and {@link Conference} IDs based on the list of
1447      * {@link Conferenceable}s passed in.
1448      *
1449      * @param conferenceables The {@link Conferenceable} connections and conferences.
1450      * @return List of string conference and call Ids.
1451      */
createIdList(List<Conferenceable> conferenceables)1452     private List<String> createIdList(List<Conferenceable> conferenceables) {
1453         List<String> ids = new ArrayList<>();
1454         for (Conferenceable c : conferenceables) {
1455             // Only allow Connection and Conference conferenceables.
1456             if (c instanceof Connection) {
1457                 Connection connection = (Connection) c;
1458                 if (mIdByConnection.containsKey(connection)) {
1459                     ids.add(mIdByConnection.get(connection));
1460                 }
1461             } else if (c instanceof Conference) {
1462                 Conference conference = (Conference) c;
1463                 if (mIdByConference.containsKey(conference)) {
1464                     ids.add(mIdByConference.get(conference));
1465                 }
1466             }
1467         }
1468         Collections.sort(ids);
1469         return ids;
1470     }
1471 
getNullConference()1472     private Conference getNullConference() {
1473         if (sNullConference == null) {
1474             sNullConference = new Conference(null) {};
1475         }
1476         return sNullConference;
1477     }
1478 
endAllConnections()1479     private void endAllConnections() {
1480         // Unbound from telecomm.  We should end all connections and conferences.
1481         for (Connection connection : mIdByConnection.keySet()) {
1482             // only operate on top-level calls. Conference calls will be removed on their own.
1483             if (connection.getConference() == null) {
1484                 connection.onDisconnect();
1485             }
1486         }
1487         for (Conference conference : mIdByConference.keySet()) {
1488             conference.onDisconnect();
1489         }
1490     }
1491 
1492     /**
1493      * Retrieves the next call ID as maintainted by the connection service.
1494      *
1495      * @return The call ID.
1496      */
getNextCallId()1497     private int getNextCallId() {
1498         synchronized(mIdSyncRoot) {
1499             return ++mId;
1500         }
1501     }
1502 }
1503