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 
105     private static Connection sNullConnection;
106 
107     private final Map<String, Connection> mConnectionById = new ConcurrentHashMap<>();
108     private final Map<Connection, String> mIdByConnection = new ConcurrentHashMap<>();
109     private final Map<String, Conference> mConferenceById = new ConcurrentHashMap<>();
110     private final Map<Conference, String> mIdByConference = new ConcurrentHashMap<>();
111     private final RemoteConnectionManager mRemoteConnectionManager =
112             new RemoteConnectionManager(this);
113     private final List<Runnable> mPreInitializationConnectionRequests = new ArrayList<>();
114     private final ConnectionServiceAdapter mAdapter = new ConnectionServiceAdapter();
115 
116     private boolean mAreAccountsInitialized = false;
117     private Conference sNullConference;
118 
119     private final IBinder mBinder = new IConnectionService.Stub() {
120         @Override
121         public void addConnectionServiceAdapter(IConnectionServiceAdapter adapter) {
122             mHandler.obtainMessage(MSG_ADD_CONNECTION_SERVICE_ADAPTER, adapter).sendToTarget();
123         }
124 
125         public void removeConnectionServiceAdapter(IConnectionServiceAdapter adapter) {
126             mHandler.obtainMessage(MSG_REMOVE_CONNECTION_SERVICE_ADAPTER, adapter).sendToTarget();
127         }
128 
129         @Override
130         public void createConnection(
131                 PhoneAccountHandle connectionManagerPhoneAccount,
132                 String id,
133                 ConnectionRequest request,
134                 boolean isIncoming,
135                 boolean isUnknown) {
136             SomeArgs args = SomeArgs.obtain();
137             args.arg1 = connectionManagerPhoneAccount;
138             args.arg2 = id;
139             args.arg3 = request;
140             args.argi1 = isIncoming ? 1 : 0;
141             args.argi2 = isUnknown ? 1 : 0;
142             mHandler.obtainMessage(MSG_CREATE_CONNECTION, args).sendToTarget();
143         }
144 
145         @Override
146         public void abort(String callId) {
147             mHandler.obtainMessage(MSG_ABORT, callId).sendToTarget();
148         }
149 
150         @Override
151         public void answerVideo(String callId, int videoState) {
152             SomeArgs args = SomeArgs.obtain();
153             args.arg1 = callId;
154             args.argi1 = videoState;
155             mHandler.obtainMessage(MSG_ANSWER_VIDEO, args).sendToTarget();
156         }
157 
158         @Override
159         public void answer(String callId) {
160             mHandler.obtainMessage(MSG_ANSWER, callId).sendToTarget();
161         }
162 
163         @Override
164         public void reject(String callId) {
165             mHandler.obtainMessage(MSG_REJECT, callId).sendToTarget();
166         }
167 
168         @Override
169         public void disconnect(String callId) {
170             mHandler.obtainMessage(MSG_DISCONNECT, callId).sendToTarget();
171         }
172 
173         @Override
174         public void hold(String callId) {
175             mHandler.obtainMessage(MSG_HOLD, callId).sendToTarget();
176         }
177 
178         @Override
179         public void unhold(String callId) {
180             mHandler.obtainMessage(MSG_UNHOLD, callId).sendToTarget();
181         }
182 
183         @Override
184         public void onCallAudioStateChanged(String callId, CallAudioState callAudioState) {
185             SomeArgs args = SomeArgs.obtain();
186             args.arg1 = callId;
187             args.arg2 = callAudioState;
188             mHandler.obtainMessage(MSG_ON_CALL_AUDIO_STATE_CHANGED, args).sendToTarget();
189         }
190 
191         @Override
192         public void playDtmfTone(String callId, char digit) {
193             mHandler.obtainMessage(MSG_PLAY_DTMF_TONE, digit, 0, callId).sendToTarget();
194         }
195 
196         @Override
197         public void stopDtmfTone(String callId) {
198             mHandler.obtainMessage(MSG_STOP_DTMF_TONE, callId).sendToTarget();
199         }
200 
201         @Override
202         public void conference(String callId1, String callId2) {
203             SomeArgs args = SomeArgs.obtain();
204             args.arg1 = callId1;
205             args.arg2 = callId2;
206             mHandler.obtainMessage(MSG_CONFERENCE, args).sendToTarget();
207         }
208 
209         @Override
210         public void splitFromConference(String callId) {
211             mHandler.obtainMessage(MSG_SPLIT_FROM_CONFERENCE, callId).sendToTarget();
212         }
213 
214         @Override
215         public void mergeConference(String callId) {
216             mHandler.obtainMessage(MSG_MERGE_CONFERENCE, callId).sendToTarget();
217         }
218 
219         @Override
220         public void swapConference(String callId) {
221             mHandler.obtainMessage(MSG_SWAP_CONFERENCE, callId).sendToTarget();
222         }
223 
224         @Override
225         public void onPostDialContinue(String callId, boolean proceed) {
226             SomeArgs args = SomeArgs.obtain();
227             args.arg1 = callId;
228             args.argi1 = proceed ? 1 : 0;
229             mHandler.obtainMessage(MSG_ON_POST_DIAL_CONTINUE, args).sendToTarget();
230         }
231     };
232 
233     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
234         @Override
235         public void handleMessage(Message msg) {
236             switch (msg.what) {
237                 case MSG_ADD_CONNECTION_SERVICE_ADAPTER:
238                     mAdapter.addAdapter((IConnectionServiceAdapter) msg.obj);
239                     onAdapterAttached();
240                     break;
241                 case MSG_REMOVE_CONNECTION_SERVICE_ADAPTER:
242                     mAdapter.removeAdapter((IConnectionServiceAdapter) msg.obj);
243                     break;
244                 case MSG_CREATE_CONNECTION: {
245                     SomeArgs args = (SomeArgs) msg.obj;
246                     try {
247                         final PhoneAccountHandle connectionManagerPhoneAccount =
248                                 (PhoneAccountHandle) args.arg1;
249                         final String id = (String) args.arg2;
250                         final ConnectionRequest request = (ConnectionRequest) args.arg3;
251                         final boolean isIncoming = args.argi1 == 1;
252                         final boolean isUnknown = args.argi2 == 1;
253                         if (!mAreAccountsInitialized) {
254                             Log.d(this, "Enqueueing pre-init request %s", id);
255                             mPreInitializationConnectionRequests.add(new Runnable() {
256                                 @Override
257                                 public void run() {
258                                     createConnection(
259                                             connectionManagerPhoneAccount,
260                                             id,
261                                             request,
262                                             isIncoming,
263                                             isUnknown);
264                                 }
265                             });
266                         } else {
267                             createConnection(
268                                     connectionManagerPhoneAccount,
269                                     id,
270                                     request,
271                                     isIncoming,
272                                     isUnknown);
273                         }
274                     } finally {
275                         args.recycle();
276                     }
277                     break;
278                 }
279                 case MSG_ABORT:
280                     abort((String) msg.obj);
281                     break;
282                 case MSG_ANSWER:
283                     answer((String) msg.obj);
284                     break;
285                 case MSG_ANSWER_VIDEO: {
286                     SomeArgs args = (SomeArgs) msg.obj;
287                     try {
288                         String callId = (String) args.arg1;
289                         int videoState = args.argi1;
290                         answerVideo(callId, videoState);
291                     } finally {
292                         args.recycle();
293                     }
294                     break;
295                 }
296                 case MSG_REJECT:
297                     reject((String) msg.obj);
298                     break;
299                 case MSG_DISCONNECT:
300                     disconnect((String) msg.obj);
301                     break;
302                 case MSG_HOLD:
303                     hold((String) msg.obj);
304                     break;
305                 case MSG_UNHOLD:
306                     unhold((String) msg.obj);
307                     break;
308                 case MSG_ON_CALL_AUDIO_STATE_CHANGED: {
309                     SomeArgs args = (SomeArgs) msg.obj;
310                     try {
311                         String callId = (String) args.arg1;
312                         CallAudioState audioState = (CallAudioState) args.arg2;
313                         onCallAudioStateChanged(callId, new CallAudioState(audioState));
314                     } finally {
315                         args.recycle();
316                     }
317                     break;
318                 }
319                 case MSG_PLAY_DTMF_TONE:
320                     playDtmfTone((String) msg.obj, (char) msg.arg1);
321                     break;
322                 case MSG_STOP_DTMF_TONE:
323                     stopDtmfTone((String) msg.obj);
324                     break;
325                 case MSG_CONFERENCE: {
326                     SomeArgs args = (SomeArgs) msg.obj;
327                     try {
328                         String callId1 = (String) args.arg1;
329                         String callId2 = (String) args.arg2;
330                         conference(callId1, callId2);
331                     } finally {
332                         args.recycle();
333                     }
334                     break;
335                 }
336                 case MSG_SPLIT_FROM_CONFERENCE:
337                     splitFromConference((String) msg.obj);
338                     break;
339                 case MSG_MERGE_CONFERENCE:
340                     mergeConference((String) msg.obj);
341                     break;
342                 case MSG_SWAP_CONFERENCE:
343                     swapConference((String) msg.obj);
344                     break;
345                 case MSG_ON_POST_DIAL_CONTINUE: {
346                     SomeArgs args = (SomeArgs) msg.obj;
347                     try {
348                         String callId = (String) args.arg1;
349                         boolean proceed = (args.argi1 == 1);
350                         onPostDialContinue(callId, proceed);
351                     } finally {
352                         args.recycle();
353                     }
354                     break;
355                 }
356                 default:
357                     break;
358             }
359         }
360     };
361 
362     private final Conference.Listener mConferenceListener = new Conference.Listener() {
363         @Override
364         public void onStateChanged(Conference conference, int oldState, int newState) {
365             String id = mIdByConference.get(conference);
366             switch (newState) {
367                 case Connection.STATE_ACTIVE:
368                     mAdapter.setActive(id);
369                     break;
370                 case Connection.STATE_HOLDING:
371                     mAdapter.setOnHold(id);
372                     break;
373                 case Connection.STATE_DISCONNECTED:
374                     // handled by onDisconnected
375                     break;
376             }
377         }
378 
379         @Override
380         public void onDisconnected(Conference conference, DisconnectCause disconnectCause) {
381             String id = mIdByConference.get(conference);
382             mAdapter.setDisconnected(id, disconnectCause);
383         }
384 
385         @Override
386         public void onConnectionAdded(Conference conference, Connection connection) {
387         }
388 
389         @Override
390         public void onConnectionRemoved(Conference conference, Connection connection) {
391         }
392 
393         @Override
394         public void onConferenceableConnectionsChanged(
395                 Conference conference, List<Connection> conferenceableConnections) {
396             mAdapter.setConferenceableConnections(
397                     mIdByConference.get(conference),
398                     createConnectionIdList(conferenceableConnections));
399         }
400 
401         @Override
402         public void onDestroyed(Conference conference) {
403             removeConference(conference);
404         }
405 
406         @Override
407         public void onConnectionCapabilitiesChanged(
408                 Conference conference,
409                 int connectionCapabilities) {
410             String id = mIdByConference.get(conference);
411             Log.d(this, "call capabilities: conference: %s",
412                     Connection.capabilitiesToString(connectionCapabilities));
413             mAdapter.setConnectionCapabilities(id, connectionCapabilities);
414         }
415 
416         @Override
417         public void onVideoStateChanged(Conference c, int videoState) {
418             String id = mIdByConference.get(c);
419             Log.d(this, "onVideoStateChanged set video state %d", videoState);
420             mAdapter.setVideoState(id, videoState);
421         }
422 
423         @Override
424         public void onVideoProviderChanged(Conference c, Connection.VideoProvider videoProvider) {
425             String id = mIdByConference.get(c);
426             Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c,
427                     videoProvider);
428             mAdapter.setVideoProvider(id, videoProvider);
429         }
430 
431         @Override
432         public void onStatusHintsChanged(Conference conference, StatusHints statusHints) {
433             String id = mIdByConference.get(conference);
434             mAdapter.setStatusHints(id, statusHints);
435         }
436 
437         @Override
438         public void onExtrasChanged(Conference conference, Bundle extras) {
439             String id = mIdByConference.get(conference);
440             mAdapter.setExtras(id, extras);
441         }
442     };
443 
444     private final Connection.Listener mConnectionListener = new Connection.Listener() {
445         @Override
446         public void onStateChanged(Connection c, int state) {
447             String id = mIdByConnection.get(c);
448             Log.d(this, "Adapter set state %s %s", id, Connection.stateToString(state));
449             switch (state) {
450                 case Connection.STATE_ACTIVE:
451                     mAdapter.setActive(id);
452                     break;
453                 case Connection.STATE_DIALING:
454                     mAdapter.setDialing(id);
455                     break;
456                 case Connection.STATE_DISCONNECTED:
457                     // Handled in onDisconnected()
458                     break;
459                 case Connection.STATE_HOLDING:
460                     mAdapter.setOnHold(id);
461                     break;
462                 case Connection.STATE_NEW:
463                     // Nothing to tell Telecom
464                     break;
465                 case Connection.STATE_RINGING:
466                     mAdapter.setRinging(id);
467                     break;
468             }
469         }
470 
471         @Override
472         public void onDisconnected(Connection c, DisconnectCause disconnectCause) {
473             String id = mIdByConnection.get(c);
474             Log.d(this, "Adapter set disconnected %s", disconnectCause);
475             mAdapter.setDisconnected(id, disconnectCause);
476         }
477 
478         @Override
479         public void onVideoStateChanged(Connection c, int videoState) {
480             String id = mIdByConnection.get(c);
481             Log.d(this, "Adapter set video state %d", videoState);
482             mAdapter.setVideoState(id, videoState);
483         }
484 
485         @Override
486         public void onAddressChanged(Connection c, Uri address, int presentation) {
487             String id = mIdByConnection.get(c);
488             mAdapter.setAddress(id, address, presentation);
489         }
490 
491         @Override
492         public void onCallerDisplayNameChanged(
493                 Connection c, String callerDisplayName, int presentation) {
494             String id = mIdByConnection.get(c);
495             mAdapter.setCallerDisplayName(id, callerDisplayName, presentation);
496         }
497 
498         @Override
499         public void onDestroyed(Connection c) {
500             removeConnection(c);
501         }
502 
503         @Override
504         public void onPostDialWait(Connection c, String remaining) {
505             String id = mIdByConnection.get(c);
506             Log.d(this, "Adapter onPostDialWait %s, %s", c, remaining);
507             mAdapter.onPostDialWait(id, remaining);
508         }
509 
510         @Override
511         public void onPostDialChar(Connection c, char nextChar) {
512             String id = mIdByConnection.get(c);
513             Log.d(this, "Adapter onPostDialChar %s, %s", c, nextChar);
514             mAdapter.onPostDialChar(id, nextChar);
515         }
516 
517         @Override
518         public void onRingbackRequested(Connection c, boolean ringback) {
519             String id = mIdByConnection.get(c);
520             Log.d(this, "Adapter onRingback %b", ringback);
521             mAdapter.setRingbackRequested(id, ringback);
522         }
523 
524         @Override
525         public void onConnectionCapabilitiesChanged(Connection c, int capabilities) {
526             String id = mIdByConnection.get(c);
527             Log.d(this, "capabilities: parcelableconnection: %s",
528                     Connection.capabilitiesToString(capabilities));
529             mAdapter.setConnectionCapabilities(id, capabilities);
530         }
531 
532         @Override
533         public void onVideoProviderChanged(Connection c, Connection.VideoProvider videoProvider) {
534             String id = mIdByConnection.get(c);
535             Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c,
536                     videoProvider);
537             mAdapter.setVideoProvider(id, videoProvider);
538         }
539 
540         @Override
541         public void onAudioModeIsVoipChanged(Connection c, boolean isVoip) {
542             String id = mIdByConnection.get(c);
543             mAdapter.setIsVoipAudioMode(id, isVoip);
544         }
545 
546         @Override
547         public void onStatusHintsChanged(Connection c, StatusHints statusHints) {
548             String id = mIdByConnection.get(c);
549             mAdapter.setStatusHints(id, statusHints);
550         }
551 
552         @Override
553         public void onConferenceablesChanged(
554                 Connection connection, List<Conferenceable> conferenceables) {
555             mAdapter.setConferenceableConnections(
556                     mIdByConnection.get(connection),
557                     createIdList(conferenceables));
558         }
559 
560         @Override
561         public void onConferenceChanged(Connection connection, Conference conference) {
562             String id = mIdByConnection.get(connection);
563             if (id != null) {
564                 String conferenceId = null;
565                 if (conference != null) {
566                     conferenceId = mIdByConference.get(conference);
567                 }
568                 mAdapter.setIsConferenced(id, conferenceId);
569             }
570         }
571 
572         @Override
573         public void onConferenceMergeFailed(Connection connection) {
574             String id = mIdByConnection.get(connection);
575             if (id != null) {
576                 mAdapter.onConferenceMergeFailed(id);
577             }
578         }
579 
580         @Override
581         public void onExtrasChanged(Connection connection, Bundle extras) {
582             String id = mIdByConnection.get(connection);
583             if (id != null) {
584                 mAdapter.setExtras(id, extras);
585             }
586         }
587     };
588 
589     /** {@inheritDoc} */
590     @Override
onBind(Intent intent)591     public final IBinder onBind(Intent intent) {
592         return mBinder;
593     }
594 
595     /** {@inheritDoc} */
596     @Override
onUnbind(Intent intent)597     public boolean onUnbind(Intent intent) {
598         endAllConnections();
599         return super.onUnbind(intent);
600     }
601 
602     /**
603      * This can be used by telecom to either create a new outgoing call or attach to an existing
604      * incoming call. In either case, telecom will cycle through a set of services and call
605      * createConnection util a connection service cancels the process or completes it successfully.
606      */
createConnection( final PhoneAccountHandle callManagerAccount, final String callId, final ConnectionRequest request, boolean isIncoming, boolean isUnknown)607     private void createConnection(
608             final PhoneAccountHandle callManagerAccount,
609             final String callId,
610             final ConnectionRequest request,
611             boolean isIncoming,
612             boolean isUnknown) {
613         Log.d(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " +
614                 "isIncoming: %b, isUnknown: %b", callManagerAccount, callId, request, isIncoming,
615                 isUnknown);
616 
617         Connection connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request)
618                 : isIncoming ? onCreateIncomingConnection(callManagerAccount, request)
619                 : onCreateOutgoingConnection(callManagerAccount, request);
620         Log.d(this, "createConnection, connection: %s", connection);
621         if (connection == null) {
622             connection = Connection.createFailedConnection(
623                     new DisconnectCause(DisconnectCause.ERROR));
624         }
625 
626         if (connection.getState() != Connection.STATE_DISCONNECTED) {
627             addConnection(callId, connection);
628         }
629 
630         Uri address = connection.getAddress();
631         String number = address == null ? "null" : address.getSchemeSpecificPart();
632         Log.v(this, "createConnection, number: %s, state: %s, capabilities: %s",
633                 Connection.toLogSafePhoneNumber(number),
634                 Connection.stateToString(connection.getState()),
635                 Connection.capabilitiesToString(connection.getConnectionCapabilities()));
636 
637         Log.d(this, "createConnection, calling handleCreateConnectionSuccessful %s", callId);
638         mAdapter.handleCreateConnectionComplete(
639                 callId,
640                 request,
641                 new ParcelableConnection(
642                         request.getAccountHandle(),
643                         connection.getState(),
644                         connection.getConnectionCapabilities(),
645                         connection.getAddress(),
646                         connection.getAddressPresentation(),
647                         connection.getCallerDisplayName(),
648                         connection.getCallerDisplayNamePresentation(),
649                         connection.getVideoProvider() == null ?
650                                 null : connection.getVideoProvider().getInterface(),
651                         connection.getVideoState(),
652                         connection.isRingbackRequested(),
653                         connection.getAudioModeIsVoip(),
654                         connection.getConnectTimeMillis(),
655                         connection.getStatusHints(),
656                         connection.getDisconnectCause(),
657                         createIdList(connection.getConferenceables()),
658                         connection.getExtras()));
659     }
660 
abort(String callId)661     private void abort(String callId) {
662         Log.d(this, "abort %s", callId);
663         findConnectionForAction(callId, "abort").onAbort();
664     }
665 
answerVideo(String callId, int videoState)666     private void answerVideo(String callId, int videoState) {
667         Log.d(this, "answerVideo %s", callId);
668         findConnectionForAction(callId, "answer").onAnswer(videoState);
669     }
670 
answer(String callId)671     private void answer(String callId) {
672         Log.d(this, "answer %s", callId);
673         findConnectionForAction(callId, "answer").onAnswer();
674     }
675 
reject(String callId)676     private void reject(String callId) {
677         Log.d(this, "reject %s", callId);
678         findConnectionForAction(callId, "reject").onReject();
679     }
680 
disconnect(String callId)681     private void disconnect(String callId) {
682         Log.d(this, "disconnect %s", callId);
683         if (mConnectionById.containsKey(callId)) {
684             findConnectionForAction(callId, "disconnect").onDisconnect();
685         } else {
686             findConferenceForAction(callId, "disconnect").onDisconnect();
687         }
688     }
689 
hold(String callId)690     private void hold(String callId) {
691         Log.d(this, "hold %s", callId);
692         if (mConnectionById.containsKey(callId)) {
693             findConnectionForAction(callId, "hold").onHold();
694         } else {
695             findConferenceForAction(callId, "hold").onHold();
696         }
697     }
698 
unhold(String callId)699     private void unhold(String callId) {
700         Log.d(this, "unhold %s", callId);
701         if (mConnectionById.containsKey(callId)) {
702             findConnectionForAction(callId, "unhold").onUnhold();
703         } else {
704             findConferenceForAction(callId, "unhold").onUnhold();
705         }
706     }
707 
onCallAudioStateChanged(String callId, CallAudioState callAudioState)708     private void onCallAudioStateChanged(String callId, CallAudioState callAudioState) {
709         Log.d(this, "onAudioStateChanged %s %s", callId, callAudioState);
710         if (mConnectionById.containsKey(callId)) {
711             findConnectionForAction(callId, "onCallAudioStateChanged").setCallAudioState(
712                     callAudioState);
713         } else {
714             findConferenceForAction(callId, "onCallAudioStateChanged").setCallAudioState(
715                     callAudioState);
716         }
717     }
718 
playDtmfTone(String callId, char digit)719     private void playDtmfTone(String callId, char digit) {
720         Log.d(this, "playDtmfTone %s %c", callId, digit);
721         if (mConnectionById.containsKey(callId)) {
722             findConnectionForAction(callId, "playDtmfTone").onPlayDtmfTone(digit);
723         } else {
724             findConferenceForAction(callId, "playDtmfTone").onPlayDtmfTone(digit);
725         }
726     }
727 
stopDtmfTone(String callId)728     private void stopDtmfTone(String callId) {
729         Log.d(this, "stopDtmfTone %s", callId);
730         if (mConnectionById.containsKey(callId)) {
731             findConnectionForAction(callId, "stopDtmfTone").onStopDtmfTone();
732         } else {
733             findConferenceForAction(callId, "stopDtmfTone").onStopDtmfTone();
734         }
735     }
736 
conference(String callId1, String callId2)737     private void conference(String callId1, String callId2) {
738         Log.d(this, "conference %s, %s", callId1, callId2);
739 
740         // Attempt to get second connection or conference.
741         Connection connection2 = findConnectionForAction(callId2, "conference");
742         Conference conference2 = getNullConference();
743         if (connection2 == getNullConnection()) {
744             conference2 = findConferenceForAction(callId2, "conference");
745             if (conference2 == getNullConference()) {
746                 Log.w(this, "Connection2 or Conference2 missing in conference request %s.",
747                         callId2);
748                 return;
749             }
750         }
751 
752         // Attempt to get first connection or conference and perform merge.
753         Connection connection1 = findConnectionForAction(callId1, "conference");
754         if (connection1 == getNullConnection()) {
755             Conference conference1 = findConferenceForAction(callId1, "addConnection");
756             if (conference1 == getNullConference()) {
757                 Log.w(this,
758                         "Connection1 or Conference1 missing in conference request %s.",
759                         callId1);
760             } else {
761                 // Call 1 is a conference.
762                 if (connection2 != getNullConnection()) {
763                     // Call 2 is a connection so merge via call 1 (conference).
764                     conference1.onMerge(connection2);
765                 } else {
766                     // Call 2 is ALSO a conference; this should never happen.
767                     Log.wtf(this, "There can only be one conference and an attempt was made to " +
768                             "merge two conferences.");
769                     return;
770                 }
771             }
772         } else {
773             // Call 1 is a connection.
774             if (conference2 != getNullConference()) {
775                 // Call 2 is a conference, so merge via call 2.
776                 conference2.onMerge(connection1);
777             } else {
778                 // Call 2 is a connection, so merge together.
779                 onConference(connection1, connection2);
780             }
781         }
782     }
783 
splitFromConference(String callId)784     private void splitFromConference(String callId) {
785         Log.d(this, "splitFromConference(%s)", callId);
786 
787         Connection connection = findConnectionForAction(callId, "splitFromConference");
788         if (connection == getNullConnection()) {
789             Log.w(this, "Connection missing in conference request %s.", callId);
790             return;
791         }
792 
793         Conference conference = connection.getConference();
794         if (conference != null) {
795             conference.onSeparate(connection);
796         }
797     }
798 
mergeConference(String callId)799     private void mergeConference(String callId) {
800         Log.d(this, "mergeConference(%s)", callId);
801         Conference conference = findConferenceForAction(callId, "mergeConference");
802         if (conference != null) {
803             conference.onMerge();
804         }
805     }
806 
swapConference(String callId)807     private void swapConference(String callId) {
808         Log.d(this, "swapConference(%s)", callId);
809         Conference conference = findConferenceForAction(callId, "swapConference");
810         if (conference != null) {
811             conference.onSwap();
812         }
813     }
814 
onPostDialContinue(String callId, boolean proceed)815     private void onPostDialContinue(String callId, boolean proceed) {
816         Log.d(this, "onPostDialContinue(%s)", callId);
817         findConnectionForAction(callId, "stopDtmfTone").onPostDialContinue(proceed);
818     }
819 
onAdapterAttached()820     private void onAdapterAttached() {
821         if (mAreAccountsInitialized) {
822             // No need to query again if we already did it.
823             return;
824         }
825 
826         mAdapter.queryRemoteConnectionServices(new RemoteServiceCallback.Stub() {
827             @Override
828             public void onResult(
829                     final List<ComponentName> componentNames,
830                     final List<IBinder> services) {
831                 mHandler.post(new Runnable() {
832                     @Override
833                     public void run() {
834                         for (int i = 0; i < componentNames.size() && i < services.size(); i++) {
835                             mRemoteConnectionManager.addConnectionService(
836                                     componentNames.get(i),
837                                     IConnectionService.Stub.asInterface(services.get(i)));
838                         }
839                         onAccountsInitialized();
840                         Log.d(this, "remote connection services found: " + services);
841                     }
842                 });
843             }
844 
845             @Override
846             public void onError() {
847                 mHandler.post(new Runnable() {
848                     @Override
849                     public void run() {
850                         mAreAccountsInitialized = true;
851                     }
852                 });
853             }
854         });
855     }
856 
857     /**
858      * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an
859      * incoming request. This is used by {@code ConnectionService}s that are registered with
860      * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} and want to be able to manage
861      * SIM-based incoming calls.
862      *
863      * @param connectionManagerPhoneAccount See description at
864      *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
865      * @param request Details about the incoming call.
866      * @return The {@code Connection} object to satisfy this call, or {@code null} to
867      *         not handle the call.
868      */
createRemoteIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)869     public final RemoteConnection createRemoteIncomingConnection(
870             PhoneAccountHandle connectionManagerPhoneAccount,
871             ConnectionRequest request) {
872         return mRemoteConnectionManager.createRemoteConnection(
873                 connectionManagerPhoneAccount, request, true);
874     }
875 
876     /**
877      * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an
878      * outgoing request. This is used by {@code ConnectionService}s that are registered with
879      * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} and want to be able to use the
880      * SIM-based {@code ConnectionService} to place its outgoing calls.
881      *
882      * @param connectionManagerPhoneAccount See description at
883      *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
884      * @param request Details about the incoming call.
885      * @return The {@code Connection} object to satisfy this call, or {@code null} to
886      *         not handle the call.
887      */
createRemoteOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)888     public final RemoteConnection createRemoteOutgoingConnection(
889             PhoneAccountHandle connectionManagerPhoneAccount,
890             ConnectionRequest request) {
891         return mRemoteConnectionManager.createRemoteConnection(
892                 connectionManagerPhoneAccount, request, false);
893     }
894 
895     /**
896      * Indicates to the relevant {@code RemoteConnectionService} that the specified
897      * {@link RemoteConnection}s should be merged into a conference call.
898      * <p>
899      * If the conference request is successful, the method {@link #onRemoteConferenceAdded} will
900      * be invoked.
901      *
902      * @param remoteConnection1 The first of the remote connections to conference.
903      * @param remoteConnection2 The second of the remote connections to conference.
904      */
conferenceRemoteConnections( RemoteConnection remoteConnection1, RemoteConnection remoteConnection2)905     public final void conferenceRemoteConnections(
906             RemoteConnection remoteConnection1,
907             RemoteConnection remoteConnection2) {
908         mRemoteConnectionManager.conferenceRemoteConnections(remoteConnection1, remoteConnection2);
909     }
910 
911     /**
912      * Adds a new conference call. When a conference call is created either as a result of an
913      * explicit request via {@link #onConference} or otherwise, the connection service should supply
914      * an instance of {@link Conference} by invoking this method. A conference call provided by this
915      * method will persist until {@link Conference#destroy} is invoked on the conference instance.
916      *
917      * @param conference The new conference object.
918      */
addConference(Conference conference)919     public final void addConference(Conference conference) {
920         Log.d(this, "addConference: conference=%s", conference);
921 
922         String id = addConferenceInternal(conference);
923         if (id != null) {
924             List<String> connectionIds = new ArrayList<>(2);
925             for (Connection connection : conference.getConnections()) {
926                 if (mIdByConnection.containsKey(connection)) {
927                     connectionIds.add(mIdByConnection.get(connection));
928                 }
929             }
930             ParcelableConference parcelableConference = new ParcelableConference(
931                     conference.getPhoneAccountHandle(),
932                     conference.getState(),
933                     conference.getConnectionCapabilities(),
934                     connectionIds,
935                     conference.getVideoProvider() == null ?
936                             null : conference.getVideoProvider().getInterface(),
937                     conference.getVideoState(),
938                     conference.getConnectTimeMillis(),
939                     conference.getStatusHints(),
940                     conference.getExtras());
941 
942             mAdapter.addConferenceCall(id, parcelableConference);
943             mAdapter.setVideoProvider(id, conference.getVideoProvider());
944             mAdapter.setVideoState(id, conference.getVideoState());
945 
946             // Go through any child calls and set the parent.
947             for (Connection connection : conference.getConnections()) {
948                 String connectionId = mIdByConnection.get(connection);
949                 if (connectionId != null) {
950                     mAdapter.setIsConferenced(connectionId, id);
951                 }
952             }
953         }
954     }
955 
956     /**
957      * Adds a connection created by the {@link ConnectionService} and informs telecom of the new
958      * connection.
959      *
960      * @param phoneAccountHandle The phone account handle for the connection.
961      * @param connection The connection to add.
962      */
addExistingConnection(PhoneAccountHandle phoneAccountHandle, Connection connection)963     public final void addExistingConnection(PhoneAccountHandle phoneAccountHandle,
964             Connection connection) {
965 
966         String id = addExistingConnectionInternal(connection);
967         if (id != null) {
968             List<String> emptyList = new ArrayList<>(0);
969 
970             ParcelableConnection parcelableConnection = new ParcelableConnection(
971                     phoneAccountHandle,
972                     connection.getState(),
973                     connection.getConnectionCapabilities(),
974                     connection.getAddress(),
975                     connection.getAddressPresentation(),
976                     connection.getCallerDisplayName(),
977                     connection.getCallerDisplayNamePresentation(),
978                     connection.getVideoProvider() == null ?
979                             null : connection.getVideoProvider().getInterface(),
980                     connection.getVideoState(),
981                     connection.isRingbackRequested(),
982                     connection.getAudioModeIsVoip(),
983                     connection.getConnectTimeMillis(),
984                     connection.getStatusHints(),
985                     connection.getDisconnectCause(),
986                     emptyList,
987                     connection.getExtras());
988             mAdapter.addExistingConnection(id, parcelableConnection);
989         }
990     }
991 
992     /**
993      * Returns all the active {@code Connection}s for which this {@code ConnectionService}
994      * has taken responsibility.
995      *
996      * @return A collection of {@code Connection}s created by this {@code ConnectionService}.
997      */
getAllConnections()998     public final Collection<Connection> getAllConnections() {
999         return mConnectionById.values();
1000     }
1001 
1002     /**
1003      * Create a {@code Connection} given an incoming request. This is used to attach to existing
1004      * incoming calls.
1005      *
1006      * @param connectionManagerPhoneAccount See description at
1007      *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
1008      * @param request Details about the incoming call.
1009      * @return The {@code Connection} object to satisfy this call, or {@code null} to
1010      *         not handle the call.
1011      */
onCreateIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1012     public Connection onCreateIncomingConnection(
1013             PhoneAccountHandle connectionManagerPhoneAccount,
1014             ConnectionRequest request) {
1015         return null;
1016     }
1017 
1018     /**
1019      * Create a {@code Connection} given an outgoing request. This is used to initiate new
1020      * outgoing calls.
1021      *
1022      * @param connectionManagerPhoneAccount The connection manager account to use for managing
1023      *         this call.
1024      *         <p>
1025      *         If this parameter is not {@code null}, it means that this {@code ConnectionService}
1026      *         has registered one or more {@code PhoneAccount}s having
1027      *         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}. This parameter will contain
1028      *         one of these {@code PhoneAccount}s, while the {@code request} will contain another
1029      *         (usually but not always distinct) {@code PhoneAccount} to be used for actually
1030      *         making the connection.
1031      *         <p>
1032      *         If this parameter is {@code null}, it means that this {@code ConnectionService} is
1033      *         being asked to make a direct connection. The
1034      *         {@link ConnectionRequest#getAccountHandle()} of parameter {@code request} will be
1035      *         a {@code PhoneAccount} registered by this {@code ConnectionService} to use for
1036      *         making the connection.
1037      * @param request Details about the outgoing call.
1038      * @return The {@code Connection} object to satisfy this call, or the result of an invocation
1039      *         of {@link Connection#createFailedConnection(DisconnectCause)} to not handle the call.
1040      */
onCreateOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1041     public Connection onCreateOutgoingConnection(
1042             PhoneAccountHandle connectionManagerPhoneAccount,
1043             ConnectionRequest request) {
1044         return null;
1045     }
1046 
1047     /**
1048      * Create a {@code Connection} for a new unknown call. An unknown call is a call originating
1049      * from the ConnectionService that was neither a user-initiated outgoing call, nor an incoming
1050      * call created using
1051      * {@code TelecomManager#addNewIncomingCall(PhoneAccountHandle, android.os.Bundle)}.
1052      *
1053      * @param connectionManagerPhoneAccount
1054      * @param request
1055      * @return
1056      *
1057      * @hide
1058      */
onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1059     public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount,
1060             ConnectionRequest request) {
1061        return null;
1062     }
1063 
1064     /**
1065      * Conference two specified connections. Invoked when the user has made a request to merge the
1066      * specified connections into a conference call. In response, the connection service should
1067      * create an instance of {@link Conference} and pass it into {@link #addConference}.
1068      *
1069      * @param connection1 A connection to merge into a conference call.
1070      * @param connection2 A connection to merge into a conference call.
1071      */
onConference(Connection connection1, Connection connection2)1072     public void onConference(Connection connection1, Connection connection2) {}
1073 
1074     /**
1075      * Indicates that a remote conference has been created for existing {@link RemoteConnection}s.
1076      * When this method is invoked, this {@link ConnectionService} should create its own
1077      * representation of the conference call and send it to telecom using {@link #addConference}.
1078      * <p>
1079      * This is only relevant to {@link ConnectionService}s which are registered with
1080      * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}.
1081      *
1082      * @param conference The remote conference call.
1083      */
onRemoteConferenceAdded(RemoteConference conference)1084     public void onRemoteConferenceAdded(RemoteConference conference) {}
1085 
1086     /**
1087      * Called when an existing connection is added remotely.
1088      * @param connection The existing connection which was added.
1089      */
onRemoteExistingConnectionAdded(RemoteConnection connection)1090     public void onRemoteExistingConnectionAdded(RemoteConnection connection) {}
1091 
1092     /**
1093      * @hide
1094      */
containsConference(Conference conference)1095     public boolean containsConference(Conference conference) {
1096         return mIdByConference.containsKey(conference);
1097     }
1098 
1099     /** {@hide} */
addRemoteConference(RemoteConference remoteConference)1100     void addRemoteConference(RemoteConference remoteConference) {
1101         onRemoteConferenceAdded(remoteConference);
1102     }
1103 
1104     /** {@hide} */
addRemoteExistingConnection(RemoteConnection remoteConnection)1105     void addRemoteExistingConnection(RemoteConnection remoteConnection) {
1106         onRemoteExistingConnectionAdded(remoteConnection);
1107     }
1108 
onAccountsInitialized()1109     private void onAccountsInitialized() {
1110         mAreAccountsInitialized = true;
1111         for (Runnable r : mPreInitializationConnectionRequests) {
1112             r.run();
1113         }
1114         mPreInitializationConnectionRequests.clear();
1115     }
1116 
1117     /**
1118      * Adds an existing connection to the list of connections, identified by a new UUID.
1119      *
1120      * @param connection The connection.
1121      * @return The UUID of the connection (e.g. the call-id).
1122      */
addExistingConnectionInternal(Connection connection)1123     private String addExistingConnectionInternal(Connection connection) {
1124         String id = UUID.randomUUID().toString();
1125         addConnection(id, connection);
1126         return id;
1127     }
1128 
addConnection(String callId, Connection connection)1129     private void addConnection(String callId, Connection connection) {
1130         mConnectionById.put(callId, connection);
1131         mIdByConnection.put(connection, callId);
1132         connection.addConnectionListener(mConnectionListener);
1133         connection.setConnectionService(this);
1134     }
1135 
1136     /** {@hide} */
removeConnection(Connection connection)1137     protected void removeConnection(Connection connection) {
1138         String id = mIdByConnection.get(connection);
1139         connection.unsetConnectionService(this);
1140         connection.removeConnectionListener(mConnectionListener);
1141         mConnectionById.remove(mIdByConnection.get(connection));
1142         mIdByConnection.remove(connection);
1143         mAdapter.removeCall(id);
1144     }
1145 
addConferenceInternal(Conference conference)1146     private String addConferenceInternal(Conference conference) {
1147         if (mIdByConference.containsKey(conference)) {
1148             Log.w(this, "Re-adding an existing conference: %s.", conference);
1149         } else if (conference != null) {
1150             String id = UUID.randomUUID().toString();
1151             mConferenceById.put(id, conference);
1152             mIdByConference.put(conference, id);
1153             conference.addListener(mConferenceListener);
1154             return id;
1155         }
1156 
1157         return null;
1158     }
1159 
removeConference(Conference conference)1160     private void removeConference(Conference conference) {
1161         if (mIdByConference.containsKey(conference)) {
1162             conference.removeListener(mConferenceListener);
1163 
1164             String id = mIdByConference.get(conference);
1165             mConferenceById.remove(id);
1166             mIdByConference.remove(conference);
1167             mAdapter.removeCall(id);
1168         }
1169     }
1170 
findConnectionForAction(String callId, String action)1171     private Connection findConnectionForAction(String callId, String action) {
1172         if (mConnectionById.containsKey(callId)) {
1173             return mConnectionById.get(callId);
1174         }
1175         Log.w(this, "%s - Cannot find Connection %s", action, callId);
1176         return getNullConnection();
1177     }
1178 
getNullConnection()1179     static synchronized Connection getNullConnection() {
1180         if (sNullConnection == null) {
1181             sNullConnection = new Connection() {};
1182         }
1183         return sNullConnection;
1184     }
1185 
findConferenceForAction(String conferenceId, String action)1186     private Conference findConferenceForAction(String conferenceId, String action) {
1187         if (mConferenceById.containsKey(conferenceId)) {
1188             return mConferenceById.get(conferenceId);
1189         }
1190         Log.w(this, "%s - Cannot find conference %s", action, conferenceId);
1191         return getNullConference();
1192     }
1193 
createConnectionIdList(List<Connection> connections)1194     private List<String> createConnectionIdList(List<Connection> connections) {
1195         List<String> ids = new ArrayList<>();
1196         for (Connection c : connections) {
1197             if (mIdByConnection.containsKey(c)) {
1198                 ids.add(mIdByConnection.get(c));
1199             }
1200         }
1201         Collections.sort(ids);
1202         return ids;
1203     }
1204 
1205     /**
1206      * Builds a list of {@link Connection} and {@link Conference} IDs based on the list of
1207      * {@link Conferenceable}s passed in.
1208      *
1209      * @param conferenceables The {@link Conferenceable} connections and conferences.
1210      * @return List of string conference and call Ids.
1211      */
createIdList(List<Conferenceable> conferenceables)1212     private List<String> createIdList(List<Conferenceable> conferenceables) {
1213         List<String> ids = new ArrayList<>();
1214         for (Conferenceable c : conferenceables) {
1215             // Only allow Connection and Conference conferenceables.
1216             if (c instanceof Connection) {
1217                 Connection connection = (Connection) c;
1218                 if (mIdByConnection.containsKey(connection)) {
1219                     ids.add(mIdByConnection.get(connection));
1220                 }
1221             } else if (c instanceof Conference) {
1222                 Conference conference = (Conference) c;
1223                 if (mIdByConference.containsKey(conference)) {
1224                     ids.add(mIdByConference.get(conference));
1225                 }
1226             }
1227         }
1228         Collections.sort(ids);
1229         return ids;
1230     }
1231 
getNullConference()1232     private Conference getNullConference() {
1233         if (sNullConference == null) {
1234             sNullConference = new Conference(null) {};
1235         }
1236         return sNullConference;
1237     }
1238 
endAllConnections()1239     private void endAllConnections() {
1240         // Unbound from telecomm.  We should end all connections and conferences.
1241         for (Connection connection : mIdByConnection.keySet()) {
1242             // only operate on top-level calls. Conference calls will be removed on their own.
1243             if (connection.getConference() == null) {
1244                 connection.onDisconnect();
1245             }
1246         }
1247         for (Conference conference : mIdByConference.keySet()) {
1248             conference.onDisconnect();
1249         }
1250     }
1251 }
1252