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