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