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