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