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