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