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