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