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