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