ConnectionServiceWrapper.java revision f62630a57de0d52be2bdbc92a9bf8f305cc0892d
1/*
2 * Copyright 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
17package com.android.server.telecom;
18
19import android.content.ComponentName;
20import android.content.Context;
21import android.net.Uri;
22import android.os.Binder;
23import android.os.Bundle;
24import android.os.IBinder;
25import android.os.RemoteException;
26import android.os.UserHandle;
27import android.telecom.CallAudioState;
28import android.telecom.Connection;
29import android.telecom.ConnectionRequest;
30import android.telecom.ConnectionService;
31import android.telecom.DisconnectCause;
32import android.telecom.GatewayInfo;
33import android.telecom.ParcelableConference;
34import android.telecom.ParcelableConnection;
35import android.telecom.PhoneAccountHandle;
36import android.telecom.StatusHints;
37import android.telecom.TelecomManager;
38import android.telecom.VideoProfile;
39
40import com.android.internal.annotations.VisibleForTesting;
41import com.android.internal.telecom.IConnectionService;
42import com.android.internal.telecom.IConnectionServiceAdapter;
43import com.android.internal.telecom.IVideoProvider;
44import com.android.internal.telecom.RemoteServiceCallback;
45import com.android.internal.util.Preconditions;
46
47import java.util.ArrayList;
48import java.util.Collections;
49import java.util.HashMap;
50import java.util.List;
51import java.util.Map;
52import java.util.Set;
53import java.util.concurrent.ConcurrentHashMap;
54
55/**
56 * Wrapper for {@link IConnectionService}s, handles binding to {@link IConnectionService} and keeps
57 * track of when the object can safely be unbound. Other classes should not use
58 * {@link IConnectionService} directly and instead should use this class to invoke methods of
59 * {@link IConnectionService}.
60 */
61@VisibleForTesting
62public class ConnectionServiceWrapper extends ServiceBinder {
63
64    private final class Adapter extends IConnectionServiceAdapter.Stub {
65
66        @Override
67        public void handleCreateConnectionComplete(
68                String callId,
69                ConnectionRequest request,
70                ParcelableConnection connection) {
71            long token = Binder.clearCallingIdentity();
72            try {
73                synchronized (mLock) {
74                    logIncoming("handleCreateConnectionComplete %s", callId);
75                    ConnectionServiceWrapper.this
76                            .handleCreateConnectionComplete(callId, request, connection);
77                }
78            } finally {
79                Binder.restoreCallingIdentity(token);
80            }
81        }
82
83        @Override
84        public void setActive(String callId) {
85            long token = Binder.clearCallingIdentity();
86            try {
87                synchronized (mLock) {
88                    logIncoming("setActive %s", callId);
89                    Call call = mCallIdMapper.getCall(callId);
90                    if (call != null) {
91                        mCallsManager.markCallAsActive(call);
92                    } else {
93                        // Log.w(this, "setActive, unknown call id: %s", msg.obj);
94                    }
95                }
96            } finally {
97                Binder.restoreCallingIdentity(token);
98            }
99        }
100
101        @Override
102        public void setRinging(String callId) {
103            long token = Binder.clearCallingIdentity();
104            try {
105                synchronized (mLock) {
106                    logIncoming("setRinging %s", callId);
107                    Call call = mCallIdMapper.getCall(callId);
108                    if (call != null) {
109                        mCallsManager.markCallAsRinging(call);
110                    } else {
111                        // Log.w(this, "setRinging, unknown call id: %s", msg.obj);
112                    }
113                }
114            } finally {
115                Binder.restoreCallingIdentity(token);
116            }
117        }
118
119        @Override
120        public void setVideoProvider(String callId, IVideoProvider videoProvider) {
121            long token = Binder.clearCallingIdentity();
122            try {
123                synchronized (mLock) {
124                    logIncoming("setVideoProvider %s", callId);
125                    Call call = mCallIdMapper.getCall(callId);
126                    if (call != null) {
127                        call.setVideoProvider(videoProvider);
128                    }
129                }
130            } finally {
131                Binder.restoreCallingIdentity(token);
132            }
133        }
134
135        @Override
136        public void setDialing(String callId) {
137            long token = Binder.clearCallingIdentity();
138            try {
139                synchronized (mLock) {
140                    logIncoming("setDialing %s", callId);
141                    Call call = mCallIdMapper.getCall(callId);
142                    if (call != null) {
143                        mCallsManager.markCallAsDialing(call);
144                    } else {
145                        // Log.w(this, "setDialing, unknown call id: %s", msg.obj);
146                    }
147                }
148            } finally {
149                Binder.restoreCallingIdentity(token);
150            }
151        }
152
153        @Override
154        public void setDisconnected(String callId, DisconnectCause disconnectCause) {
155            long token = Binder.clearCallingIdentity();
156            try {
157                synchronized (mLock) {
158                    logIncoming("setDisconnected %s %s", callId, disconnectCause);
159                    Call call = mCallIdMapper.getCall(callId);
160                    Log.d(this, "disconnect call %s %s", disconnectCause, call);
161                    if (call != null) {
162                        mCallsManager.markCallAsDisconnected(call, disconnectCause);
163                    } else {
164                        // Log.w(this, "setDisconnected, unknown call id: %s", args.arg1);
165                    }
166                }
167            } finally {
168                Binder.restoreCallingIdentity(token);
169            }
170        }
171
172        @Override
173        public void setOnHold(String callId) {
174            long token = Binder.clearCallingIdentity();
175            try {
176                synchronized (mLock) {
177                    logIncoming("setOnHold %s", callId);
178                    Call call = mCallIdMapper.getCall(callId);
179                    if (call != null) {
180                        mCallsManager.markCallAsOnHold(call);
181                    } else {
182                        // Log.w(this, "setOnHold, unknown call id: %s", msg.obj);
183                    }
184                }
185            } finally {
186                Binder.restoreCallingIdentity(token);
187            }
188        }
189
190        @Override
191        public void setRingbackRequested(String callId, boolean ringback) {
192            long token = Binder.clearCallingIdentity();
193            try {
194                synchronized (mLock) {
195                    logIncoming("setRingbackRequested %s %b", callId, ringback);
196                    Call call = mCallIdMapper.getCall(callId);
197                    if (call != null) {
198                        call.setRingbackRequested(ringback);
199                    } else {
200                        // Log.w(this, "setRingback, unknown call id: %s", args.arg1);
201                    }
202                }
203            } finally {
204                Binder.restoreCallingIdentity(token);
205            }
206        }
207
208        @Override
209        public void removeCall(String callId) {
210            long token = Binder.clearCallingIdentity();
211            try {
212                synchronized (mLock) {
213                    logIncoming("removeCall %s", callId);
214                    Call call = mCallIdMapper.getCall(callId);
215                    if (call != null) {
216                        if (call.isAlive()) {
217                            mCallsManager.markCallAsDisconnected(
218                                    call, new DisconnectCause(DisconnectCause.REMOTE));
219                        } else {
220                            mCallsManager.markCallAsRemoved(call);
221                        }
222                    }
223                }
224            } finally {
225                Binder.restoreCallingIdentity(token);
226            }
227        }
228
229        @Override
230        public void setConnectionCapabilities(String callId, int connectionCapabilities) {
231            long token = Binder.clearCallingIdentity();
232            try {
233                synchronized (mLock) {
234                    logIncoming("setConnectionCapabilities %s %d", callId, connectionCapabilities);
235                    Call call = mCallIdMapper.getCall(callId);
236                    if (call != null) {
237                        call.setConnectionCapabilities(connectionCapabilities);
238                    } else {
239                        // Log.w(ConnectionServiceWrapper.this,
240                        // "setConnectionCapabilities, unknown call id: %s", msg.obj);
241                    }
242                }
243            } finally {
244                Binder.restoreCallingIdentity(token);
245            }
246        }
247
248        @Override
249        public void setIsConferenced(String callId, String conferenceCallId) {
250            long token = Binder.clearCallingIdentity();
251            try {
252                synchronized (mLock) {
253                    logIncoming("setIsConferenced %s %s", callId, conferenceCallId);
254                    Call childCall = mCallIdMapper.getCall(callId);
255                    if (childCall != null) {
256                        if (conferenceCallId == null) {
257                            Log.d(this, "unsetting parent: %s", conferenceCallId);
258                            childCall.setParentCall(null);
259                        } else {
260                            Call conferenceCall = mCallIdMapper.getCall(conferenceCallId);
261                            childCall.setParentCall(conferenceCall);
262                        }
263                    } else {
264                        // Log.w(this, "setIsConferenced, unknown call id: %s", args.arg1);
265                    }
266                }
267            } finally {
268                Binder.restoreCallingIdentity(token);
269            }
270        }
271
272        @Override
273        public void setConferenceMergeFailed(String callId) {
274            long token = Binder.clearCallingIdentity();
275            try {
276                synchronized (mLock) {
277                    logIncoming("setConferenceMergeFailed %s", callId);
278                    // TODO: we should move the UI for indication a merge failure here
279                    // from CallNotifier.onSuppServiceFailed(). This way the InCallUI can
280                    // deliver the message anyway that they want. b/20530631.
281                    Call call = mCallIdMapper.getCall(callId);
282                    if (call != null) {
283                        // Just refresh the connection capabilities so that the UI
284                        // is forced to reenable the merge button as the capability
285                        // is still on the connection. Note when b/20530631 is fixed, we need
286                        // to revisit this fix to remove this hacky way of unhiding the merge
287                        // button (side effect of reprocessing the capabilities) and plumb
288                        // the failure event all the way to InCallUI instead of stopping
289                        // it here. That way we can also handle the UI of notifying that
290                        // the merged has failed.
291                        call.setConnectionCapabilities(call.getConnectionCapabilities(), true);
292                    } else {
293                        Log.w(this, "setConferenceMergeFailed, unknown call id: %s", callId);
294                    }
295                }
296            } finally {
297                Binder.restoreCallingIdentity(token);
298            }
299        }
300
301        @Override
302        public void addConferenceCall(String callId, ParcelableConference parcelableConference) {
303            long token = Binder.clearCallingIdentity();
304            try {
305                synchronized (mLock) {
306                    if (mCallIdMapper.getCall(callId) != null) {
307                        Log.w(this, "Attempting to add a conference call using an existing " +
308                                "call id %s", callId);
309                        return;
310                    }
311
312                    // Make sure that there's at least one valid call. For remote connections
313                    // we'll get a add conference msg from both the remote connection service
314                    // and from the real connection service.
315                    boolean hasValidCalls = false;
316                    for (String connId : parcelableConference.getConnectionIds()) {
317                        if (mCallIdMapper.getCall(connId) != null) {
318                            hasValidCalls = true;
319                        }
320                    }
321                    // But don't bail out if the connection count is 0, because that is a valid
322                    // IMS conference state.
323                    if (!hasValidCalls && parcelableConference.getConnectionIds().size() > 0) {
324                        Log.d(this, "Attempting to add a conference with no valid calls");
325                        return;
326                    }
327
328                    // need to create a new Call
329                    PhoneAccountHandle phAcc = null;
330                    if (parcelableConference != null &&
331                            parcelableConference.getPhoneAccount() != null) {
332                        phAcc = parcelableConference.getPhoneAccount();
333                    }
334                    Call conferenceCall = mCallsManager.createConferenceCall(callId,
335                            phAcc, parcelableConference);
336                    mCallIdMapper.addCall(conferenceCall, callId);
337                    conferenceCall.setConnectionService(ConnectionServiceWrapper.this);
338
339                    Log.d(this, "adding children to conference %s phAcc %s",
340                            parcelableConference.getConnectionIds(), phAcc);
341                    for (String connId : parcelableConference.getConnectionIds()) {
342                        Call childCall = mCallIdMapper.getCall(connId);
343                        Log.d(this, "found child: %s", connId);
344                        if (childCall != null) {
345                            childCall.setParentCall(conferenceCall);
346                        }
347                    }
348                }
349            } finally {
350                Binder.restoreCallingIdentity(token);
351            }
352        }
353
354        @Override
355        public void onPostDialWait(String callId, String remaining) throws RemoteException {
356            long token = Binder.clearCallingIdentity();
357            try {
358                synchronized (mLock) {
359                    logIncoming("onPostDialWait %s %s", callId, remaining);
360                    Call call = mCallIdMapper.getCall(callId);
361                    if (call != null) {
362                        call.onPostDialWait(remaining);
363                    } else {
364                        // Log.w(this, "onPostDialWait, unknown call id: %s", args.arg1);
365                    }
366                }
367            } finally {
368                Binder.restoreCallingIdentity(token);
369            }
370        }
371
372        @Override
373        public void onPostDialChar(String callId, char nextChar) throws RemoteException {
374            long token = Binder.clearCallingIdentity();
375            try {
376                synchronized (mLock) {
377                    logIncoming("onPostDialChar %s %s", callId, nextChar);
378                    Call call = mCallIdMapper.getCall(callId);
379                    if (call != null) {
380                        call.onPostDialChar(nextChar);
381                    } else {
382                        // Log.w(this, "onPostDialChar, unknown call id: %s", args.arg1);
383                    }
384                }
385            } finally {
386                Binder.restoreCallingIdentity(token);
387            }
388        }
389
390        @Override
391        public void queryRemoteConnectionServices(RemoteServiceCallback callback) {
392            long token = Binder.clearCallingIdentity();
393            try {
394                synchronized (mLock) {
395                    logIncoming("queryRemoteConnectionServices %s", callback);
396                    ConnectionServiceWrapper.this.queryRemoteConnectionServices(callback);
397                }
398            } finally {
399                Binder.restoreCallingIdentity(token);
400            }
401        }
402
403        @Override
404        public void setVideoState(String callId, int videoState) {
405            long token = Binder.clearCallingIdentity();
406            try {
407                synchronized (mLock) {
408                    logIncoming("setVideoState %s %d", callId, videoState);
409                    Call call = mCallIdMapper.getCall(callId);
410                    if (call != null) {
411                        call.setVideoState(videoState);
412                    }
413                }
414            } finally {
415                Binder.restoreCallingIdentity(token);
416            }
417        }
418
419        @Override
420        public void setIsVoipAudioMode(String callId, boolean isVoip) {
421            long token = Binder.clearCallingIdentity();
422            try {
423                synchronized (mLock) {
424                    logIncoming("setIsVoipAudioMode %s %b", callId, isVoip);
425                    Call call = mCallIdMapper.getCall(callId);
426                    if (call != null) {
427                        call.setIsVoipAudioMode(isVoip);
428                    }
429                }
430            } finally {
431                Binder.restoreCallingIdentity(token);
432            }
433        }
434
435        @Override
436        public void setStatusHints(String callId, StatusHints statusHints) {
437            long token = Binder.clearCallingIdentity();
438            try {
439                synchronized (mLock) {
440                    logIncoming("setStatusHints %s %s", callId, statusHints);
441                    Call call = mCallIdMapper.getCall(callId);
442                    if (call != null) {
443                        call.setStatusHints(statusHints);
444                    }
445                }
446            } finally {
447                Binder.restoreCallingIdentity(token);
448            }
449        }
450
451        @Override
452        public void setExtras(String callId, Bundle extras) {
453            long token = Binder.clearCallingIdentity();
454            try {
455                synchronized(mLock) {
456                    logIncoming("setExtras %s %s", callId, extras);
457                    Call call = mCallIdMapper.getCall(callId);
458                    if (call != null) {
459                        call.setExtras(extras);
460                    }
461                }
462            } finally {
463                Binder.restoreCallingIdentity(token);
464            }
465        }
466
467        @Override
468        public void setAddress(String callId, Uri address, int presentation) {
469            long token = Binder.clearCallingIdentity();
470            try {
471                synchronized (mLock) {
472                    logIncoming("setAddress %s %s %d", callId, address, presentation);
473                    Call call = mCallIdMapper.getCall(callId);
474                    if (call != null) {
475                        call.setHandle(address, presentation);
476                    }
477                }
478            } finally {
479                Binder.restoreCallingIdentity(token);
480            }
481        }
482
483        @Override
484        public void setCallerDisplayName(
485                String callId, String callerDisplayName, int presentation) {
486            long token = Binder.clearCallingIdentity();
487            try {
488                synchronized (mLock) {
489                    logIncoming("setCallerDisplayName %s %s %d", callId, callerDisplayName,
490                            presentation);
491                    Call call = mCallIdMapper.getCall(callId);
492                    if (call != null) {
493                        call.setCallerDisplayName(callerDisplayName, presentation);
494                    }
495                }
496            } finally {
497                Binder.restoreCallingIdentity(token);
498            }
499        }
500
501        @Override
502        public void setConferenceableConnections(
503                String callId, List<String> conferenceableCallIds) {
504            long token = Binder.clearCallingIdentity();
505            try {
506                synchronized (mLock) {
507                    logIncoming("setConferenceableConnections %s %s", callId,
508                            conferenceableCallIds);
509                    Call call = mCallIdMapper.getCall(callId);
510                    if (call != null) {
511                        List<Call> conferenceableCalls =
512                                new ArrayList<>(conferenceableCallIds.size());
513                        for (String otherId : conferenceableCallIds) {
514                            Call otherCall = mCallIdMapper.getCall(otherId);
515                            if (otherCall != null && otherCall != call) {
516                                conferenceableCalls.add(otherCall);
517                            }
518                        }
519                        call.setConferenceableCalls(conferenceableCalls);
520                    }
521                }
522            } finally {
523                Binder.restoreCallingIdentity(token);
524            }
525        }
526
527        @Override
528        public void addExistingConnection(String callId, ParcelableConnection connection) {
529            long token = Binder.clearCallingIdentity();
530            try {
531                synchronized (mLock) {
532                    logIncoming("addExistingConnection  %s %s", callId, connection);
533                    Call existingCall = mCallsManager
534                            .createCallForExistingConnection(callId, connection);
535                    mCallIdMapper.addCall(existingCall, callId);
536                    existingCall.setConnectionService(ConnectionServiceWrapper.this);
537                }
538            } finally {
539                Binder.restoreCallingIdentity(token);
540            }
541        }
542    }
543
544    private final Adapter mAdapter = new Adapter();
545    private final CallIdMapper mCallIdMapper = new CallIdMapper();
546    private final Map<String, CreateConnectionResponse> mPendingResponses = new HashMap<>();
547
548    private Binder2 mBinder = new Binder2();
549    private IConnectionService mServiceInterface;
550    private final ConnectionServiceRepository mConnectionServiceRepository;
551    private final PhoneAccountRegistrar mPhoneAccountRegistrar;
552    private final CallsManager mCallsManager;
553
554    /**
555     * Creates a connection service.
556     *
557     * @param componentName The component name of the service with which to bind.
558     * @param connectionServiceRepository Connection service repository.
559     * @param phoneAccountRegistrar Phone account registrar
560     * @param callsManager Calls manager
561     * @param context The context.
562     * @param userHandle The {@link UserHandle} to use when binding.
563     */
564    ConnectionServiceWrapper(
565            ComponentName componentName,
566            ConnectionServiceRepository connectionServiceRepository,
567            PhoneAccountRegistrar phoneAccountRegistrar,
568            CallsManager callsManager,
569            Context context,
570            TelecomSystem.SyncRoot lock,
571            UserHandle userHandle) {
572        super(ConnectionService.SERVICE_INTERFACE, componentName, context, lock, userHandle);
573        mConnectionServiceRepository = connectionServiceRepository;
574        phoneAccountRegistrar.addListener(new PhoneAccountRegistrar.Listener() {
575            // TODO -- Upon changes to PhoneAccountRegistrar, need to re-wire connections
576            // To do this, we must proxy remote ConnectionService objects
577        });
578        mPhoneAccountRegistrar = phoneAccountRegistrar;
579        mCallsManager = callsManager;
580    }
581
582    /** See {@link IConnectionService#addConnectionServiceAdapter}. */
583    private void addConnectionServiceAdapter(IConnectionServiceAdapter adapter) {
584        if (isServiceValid("addConnectionServiceAdapter")) {
585            try {
586                logOutgoing("addConnectionServiceAdapter %s", adapter);
587                mServiceInterface.addConnectionServiceAdapter(adapter);
588            } catch (RemoteException e) {
589            }
590        }
591    }
592
593    /**
594     * Creates a new connection for a new outgoing call or to attach to an existing incoming call.
595     */
596    void createConnection(final Call call, final CreateConnectionResponse response) {
597        Log.d(this, "createConnection(%s) via %s.", call, getComponentName());
598        BindCallback callback = new BindCallback() {
599            @Override
600            public void onSuccess() {
601                String callId = mCallIdMapper.getCallId(call);
602                mPendingResponses.put(callId, response);
603
604                GatewayInfo gatewayInfo = call.getGatewayInfo();
605                Bundle extras = call.getIntentExtras();
606                if (gatewayInfo != null && gatewayInfo.getGatewayProviderPackageName() != null &&
607                        gatewayInfo.getOriginalAddress() != null) {
608                    extras = (Bundle) extras.clone();
609                    extras.putString(
610                            TelecomManager.GATEWAY_PROVIDER_PACKAGE,
611                            gatewayInfo.getGatewayProviderPackageName());
612                    extras.putParcelable(
613                            TelecomManager.GATEWAY_ORIGINAL_ADDRESS,
614                            gatewayInfo.getOriginalAddress());
615                }
616
617                Log.event(call, Log.Events.START_CONNECTION, Log.piiHandle(call.getHandle()));
618                try {
619                    mServiceInterface.createConnection(
620                            call.getConnectionManagerPhoneAccount(),
621                            callId,
622                            new ConnectionRequest(
623                                    call.getTargetPhoneAccount(),
624                                    call.getHandle(),
625                                    extras,
626                                    call.getVideoState(),
627                                    callId),
628                            call.isIncoming(),
629                            call.isUnknown());
630                } catch (RemoteException e) {
631                    Log.e(this, e, "Failure to createConnection -- %s", getComponentName());
632                    mPendingResponses.remove(callId).handleCreateConnectionFailure(
633                            new DisconnectCause(DisconnectCause.ERROR, e.toString()));
634                }
635            }
636
637            @Override
638            public void onFailure() {
639                Log.e(this, new Exception(), "Failure to call %s", getComponentName());
640                response.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.ERROR));
641            }
642        };
643
644        mBinder.bind(callback, call);
645    }
646
647    /** @see IConnectionService#abort(String) */
648    void abort(Call call) {
649        // Clear out any pending outgoing call data
650        final String callId = mCallIdMapper.getCallId(call);
651
652        // If still bound, tell the connection service to abort.
653        if (callId != null && isServiceValid("abort")) {
654            try {
655                logOutgoing("abort %s", callId);
656                mServiceInterface.abort(callId);
657            } catch (RemoteException e) {
658            }
659        }
660
661        removeCall(call, new DisconnectCause(DisconnectCause.LOCAL));
662    }
663
664    /** @see IConnectionService#hold(String) */
665    void hold(Call call) {
666        final String callId = mCallIdMapper.getCallId(call);
667        if (callId != null && isServiceValid("hold")) {
668            try {
669                logOutgoing("hold %s", callId);
670                mServiceInterface.hold(callId);
671            } catch (RemoteException e) {
672            }
673        }
674    }
675
676    /** @see IConnectionService#unhold(String) */
677    void unhold(Call call) {
678        final String callId = mCallIdMapper.getCallId(call);
679        if (callId != null && isServiceValid("unhold")) {
680            try {
681                logOutgoing("unhold %s", callId);
682                mServiceInterface.unhold(callId);
683            } catch (RemoteException e) {
684            }
685        }
686    }
687
688    /** @see IConnectionService#onCallAudioStateChanged(String,CallAudioState) */
689    @VisibleForTesting
690    public void onCallAudioStateChanged(Call activeCall, CallAudioState audioState) {
691        final String callId = mCallIdMapper.getCallId(activeCall);
692        if (callId != null && isServiceValid("onCallAudioStateChanged")) {
693            try {
694                logOutgoing("onCallAudioStateChanged %s %s", callId, audioState);
695                mServiceInterface.onCallAudioStateChanged(callId, audioState);
696            } catch (RemoteException e) {
697            }
698        }
699    }
700
701    /** @see IConnectionService#disconnect(String) */
702    void disconnect(Call call) {
703        final String callId = mCallIdMapper.getCallId(call);
704        if (callId != null && isServiceValid("disconnect")) {
705            try {
706                logOutgoing("disconnect %s", callId);
707                mServiceInterface.disconnect(callId);
708            } catch (RemoteException e) {
709            }
710        }
711    }
712
713    /** @see IConnectionService#answer(String) */
714    void answer(Call call, int videoState) {
715        final String callId = mCallIdMapper.getCallId(call);
716        if (callId != null && isServiceValid("answer")) {
717            try {
718                logOutgoing("answer %s %d", callId, videoState);
719                if (VideoProfile.isAudioOnly(videoState)) {
720                    mServiceInterface.answer(callId);
721                } else {
722                    mServiceInterface.answerVideo(callId, videoState);
723                }
724            } catch (RemoteException e) {
725            }
726        }
727    }
728
729    /** @see IConnectionService#reject(String) */
730    void reject(Call call, boolean rejectWithMessage, String message) {
731        final String callId = mCallIdMapper.getCallId(call);
732        if (callId != null && isServiceValid("reject")) {
733            try {
734                logOutgoing("reject %s", callId);
735
736                if (rejectWithMessage && call.can(
737                        android.telecom.Call.Details.CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION)) {
738                    mServiceInterface.rejectWithMessage(callId, message);
739                } else {
740                    mServiceInterface.reject(callId);
741                }
742            } catch (RemoteException e) {
743            }
744        }
745    }
746
747    /** @see IConnectionService#playDtmfTone(String,char) */
748    void playDtmfTone(Call call, char digit) {
749        final String callId = mCallIdMapper.getCallId(call);
750        if (callId != null && isServiceValid("playDtmfTone")) {
751            try {
752                logOutgoing("playDtmfTone %s %c", callId, digit);
753                mServiceInterface.playDtmfTone(callId, digit);
754            } catch (RemoteException e) {
755            }
756        }
757    }
758
759    /** @see IConnectionService#stopDtmfTone(String) */
760    void stopDtmfTone(Call call) {
761        final String callId = mCallIdMapper.getCallId(call);
762        if (callId != null && isServiceValid("stopDtmfTone")) {
763            try {
764                logOutgoing("stopDtmfTone %s",callId);
765                mServiceInterface.stopDtmfTone(callId);
766            } catch (RemoteException e) {
767            }
768        }
769    }
770
771    void addCall(Call call) {
772        if (mCallIdMapper.getCallId(call) == null) {
773            mCallIdMapper.addCall(call);
774        }
775    }
776
777    /**
778     * Associates newCall with this connection service by replacing callToReplace.
779     */
780    void replaceCall(Call newCall, Call callToReplace) {
781        Preconditions.checkState(callToReplace.getConnectionService() == this);
782        mCallIdMapper.replaceCall(newCall, callToReplace);
783    }
784
785    void removeCall(Call call) {
786        removeCall(call, new DisconnectCause(DisconnectCause.ERROR));
787    }
788
789    void removeCall(String callId, DisconnectCause disconnectCause) {
790        CreateConnectionResponse response = mPendingResponses.remove(callId);
791        if (response != null) {
792            response.handleCreateConnectionFailure(disconnectCause);
793        }
794
795        mCallIdMapper.removeCall(callId);
796    }
797
798    void removeCall(Call call, DisconnectCause disconnectCause) {
799        CreateConnectionResponse response = mPendingResponses.remove(mCallIdMapper.getCallId(call));
800        if (response != null) {
801            response.handleCreateConnectionFailure(disconnectCause);
802        }
803
804        mCallIdMapper.removeCall(call);
805    }
806
807    void onPostDialContinue(Call call, boolean proceed) {
808        final String callId = mCallIdMapper.getCallId(call);
809        if (callId != null && isServiceValid("onPostDialContinue")) {
810            try {
811                logOutgoing("onPostDialContinue %s %b", callId, proceed);
812                mServiceInterface.onPostDialContinue(callId, proceed);
813            } catch (RemoteException ignored) {
814            }
815        }
816    }
817
818    void conference(final Call call, Call otherCall) {
819        final String callId = mCallIdMapper.getCallId(call);
820        final String otherCallId = mCallIdMapper.getCallId(otherCall);
821        if (callId != null && otherCallId != null && isServiceValid("conference")) {
822            try {
823                logOutgoing("conference %s %s", callId, otherCallId);
824                mServiceInterface.conference(callId, otherCallId);
825            } catch (RemoteException ignored) {
826            }
827        }
828    }
829
830    void splitFromConference(Call call) {
831        final String callId = mCallIdMapper.getCallId(call);
832        if (callId != null && isServiceValid("splitFromConference")) {
833            try {
834                logOutgoing("splitFromConference %s", callId);
835                mServiceInterface.splitFromConference(callId);
836            } catch (RemoteException ignored) {
837            }
838        }
839    }
840
841    void mergeConference(Call call) {
842        final String callId = mCallIdMapper.getCallId(call);
843        if (callId != null && isServiceValid("mergeConference")) {
844            try {
845                logOutgoing("mergeConference %s", callId);
846                mServiceInterface.mergeConference(callId);
847            } catch (RemoteException ignored) {
848            }
849        }
850    }
851
852    void swapConference(Call call) {
853        final String callId = mCallIdMapper.getCallId(call);
854        if (callId != null && isServiceValid("swapConference")) {
855            try {
856                logOutgoing("swapConference %s", callId);
857                mServiceInterface.swapConference(callId);
858            } catch (RemoteException ignored) {
859            }
860        }
861    }
862
863    /** {@inheritDoc} */
864    @Override
865    protected void setServiceInterface(IBinder binder) {
866        if (binder == null) {
867            // We have lost our service connection. Notify the world that this service is done.
868            // We must notify the adapter before CallsManager. The adapter will force any pending
869            // outgoing calls to try the next service. This needs to happen before CallsManager
870            // tries to clean up any calls still associated with this service.
871            handleConnectionServiceDeath();
872            mCallsManager.handleConnectionServiceDeath(this);
873            mServiceInterface = null;
874        } else {
875            mServiceInterface = IConnectionService.Stub.asInterface(binder);
876            addConnectionServiceAdapter(mAdapter);
877        }
878    }
879
880    private void handleCreateConnectionComplete(
881            String callId,
882            ConnectionRequest request,
883            ParcelableConnection connection) {
884        // TODO: Note we are not using parameter "request", which is a side effect of our tacit
885        // assumption that we have at most one outgoing connection attempt per ConnectionService.
886        // This may not continue to be the case.
887        if (connection.getState() == Connection.STATE_DISCONNECTED) {
888            // A connection that begins in the DISCONNECTED state is an indication of
889            // failure to connect; we handle all failures uniformly
890            removeCall(callId, connection.getDisconnectCause());
891        } else {
892            // Successful connection
893            if (mPendingResponses.containsKey(callId)) {
894                mPendingResponses.remove(callId)
895                        .handleCreateConnectionSuccess(mCallIdMapper, connection);
896            }
897        }
898    }
899
900    /**
901     * Called when the associated connection service dies.
902     */
903    private void handleConnectionServiceDeath() {
904        if (!mPendingResponses.isEmpty()) {
905            CreateConnectionResponse[] responses = mPendingResponses.values().toArray(
906                    new CreateConnectionResponse[mPendingResponses.values().size()]);
907            mPendingResponses.clear();
908            for (int i = 0; i < responses.length; i++) {
909                responses[i].handleCreateConnectionFailure(
910                        new DisconnectCause(DisconnectCause.ERROR));
911            }
912        }
913        mCallIdMapper.clear();
914    }
915
916    private void logIncoming(String msg, Object... params) {
917        Log.d(this, "ConnectionService -> Telecom: " + msg, params);
918    }
919
920    private void logOutgoing(String msg, Object... params) {
921        Log.d(this, "Telecom -> ConnectionService: " + msg, params);
922    }
923
924    private void queryRemoteConnectionServices(final RemoteServiceCallback callback) {
925        // Only give remote connection services to this connection service if it is listed as
926        // the connection manager.
927        PhoneAccountHandle simCallManager = mPhoneAccountRegistrar.getSimCallManager();
928        Log.d(this, "queryRemoteConnectionServices finds simCallManager = %s", simCallManager);
929        if (simCallManager == null ||
930                !simCallManager.getComponentName().equals(getComponentName())) {
931            noRemoteServices(callback);
932            return;
933        }
934
935        // Make a list of ConnectionServices that are listed as being associated with SIM accounts
936        final Set<ConnectionServiceWrapper> simServices = Collections.newSetFromMap(
937                new ConcurrentHashMap<ConnectionServiceWrapper, Boolean>(8, 0.9f, 1));
938        for (PhoneAccountHandle handle : mPhoneAccountRegistrar.getSimPhoneAccounts()) {
939            ConnectionServiceWrapper service = mConnectionServiceRepository.getService(
940                    handle.getComponentName(), handle.getUserHandle());
941            if (service != null) {
942                simServices.add(service);
943            }
944        }
945
946        final List<ComponentName> simServiceComponentNames = new ArrayList<>();
947        final List<IBinder> simServiceBinders = new ArrayList<>();
948
949        Log.v(this, "queryRemoteConnectionServices, simServices = %s", simServices);
950
951        for (ConnectionServiceWrapper simService : simServices) {
952            if (simService == this) {
953                // Only happens in the unlikely case that a SIM service is also a SIM call manager
954                continue;
955            }
956
957            final ConnectionServiceWrapper currentSimService = simService;
958
959            currentSimService.mBinder.bind(new BindCallback() {
960                @Override
961                public void onSuccess() {
962                    Log.d(this, "Adding simService %s", currentSimService.getComponentName());
963                    simServiceComponentNames.add(currentSimService.getComponentName());
964                    simServiceBinders.add(currentSimService.mServiceInterface.asBinder());
965                    maybeComplete();
966                }
967
968                @Override
969                public void onFailure() {
970                    Log.d(this, "Failed simService %s", currentSimService.getComponentName());
971                    // We know maybeComplete() will always be a no-op from now on, so go ahead and
972                    // signal failure of the entire request
973                    noRemoteServices(callback);
974                }
975
976                private void maybeComplete() {
977                    if (simServiceComponentNames.size() == simServices.size()) {
978                        setRemoteServices(callback, simServiceComponentNames, simServiceBinders);
979                    }
980                }
981            }, null);
982        }
983    }
984
985    private void setRemoteServices(
986            RemoteServiceCallback callback,
987            List<ComponentName> componentNames,
988            List<IBinder> binders) {
989        try {
990            callback.onResult(componentNames, binders);
991        } catch (RemoteException e) {
992            Log.e(this, e, "Contacting ConnectionService %s",
993                    ConnectionServiceWrapper.this.getComponentName());
994        }
995    }
996
997    private void noRemoteServices(RemoteServiceCallback callback) {
998        setRemoteServices(callback, Collections.EMPTY_LIST, Collections.EMPTY_LIST);
999    }
1000}
1001