| /* | 
 |  * Copyright (C) 2014 The Android Open Source Project | 
 |  * | 
 |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
 |  * you may not use this file except in compliance with the License. | 
 |  * You may obtain a copy of the License at | 
 |  * | 
 |  *      http://www.apache.org/licenses/LICENSE-2.0 | 
 |  * | 
 |  * Unless required by applicable law or agreed to in writing, software | 
 |  * distributed under the License is distributed on an "AS IS" BASIS, | 
 |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 |  * See the License for the specific language governing permissions and | 
 |  * limitations under the License. | 
 |  */ | 
 |  | 
 | package android.telecom; | 
 |  | 
 | import android.net.Uri; | 
 | import android.os.Bundle; | 
 | import android.os.IBinder; | 
 | import android.os.IBinder.DeathRecipient; | 
 | import android.os.RemoteException; | 
 | import android.os.ResultReceiver; | 
 | import android.telecom.Logging.Session; | 
 |  | 
 | import com.android.internal.telecom.IConnectionService; | 
 | import com.android.internal.telecom.IConnectionServiceAdapter; | 
 | import com.android.internal.telecom.IVideoProvider; | 
 | import com.android.internal.telecom.RemoteServiceCallback; | 
 |  | 
 | import java.util.ArrayList; | 
 | import java.util.HashMap; | 
 | import java.util.HashSet; | 
 | import java.util.List; | 
 | import java.util.Map; | 
 | import java.util.Set; | 
 | import java.util.UUID; | 
 |  | 
 | /** | 
 |  * Remote connection service which other connection services can use to place calls on their behalf. | 
 |  * | 
 |  * @hide | 
 |  */ | 
 | final class RemoteConnectionService { | 
 |  | 
 |     // Note: Casting null to avoid ambiguous constructor reference. | 
 |     private static final RemoteConnection NULL_CONNECTION = | 
 |             new RemoteConnection("NULL", null, (ConnectionRequest) null); | 
 |  | 
 |     private static final RemoteConference NULL_CONFERENCE = | 
 |             new RemoteConference("NULL", null); | 
 |  | 
 |     private final IConnectionServiceAdapter mServantDelegate = new IConnectionServiceAdapter() { | 
 |         @Override | 
 |         public void handleCreateConnectionComplete( | 
 |                 String id, | 
 |                 ConnectionRequest request, | 
 |                 ParcelableConnection parcel, | 
 |                 Session.Info info) { | 
 |             RemoteConnection connection = | 
 |                     findConnectionForAction(id, "handleCreateConnectionSuccessful"); | 
 |             if (connection != NULL_CONNECTION && mPendingConnections.contains(connection)) { | 
 |                 mPendingConnections.remove(connection); | 
 |                 // Unconditionally initialize the connection ... | 
 |                 connection.setConnectionCapabilities(parcel.getConnectionCapabilities()); | 
 |                 connection.setConnectionProperties(parcel.getConnectionProperties()); | 
 |                 if (parcel.getHandle() != null | 
 |                     || parcel.getState() != Connection.STATE_DISCONNECTED) { | 
 |                     connection.setAddress(parcel.getHandle(), parcel.getHandlePresentation()); | 
 |                 } | 
 |                 if (parcel.getCallerDisplayName() != null | 
 |                     || parcel.getState() != Connection.STATE_DISCONNECTED) { | 
 |                     connection.setCallerDisplayName( | 
 |                             parcel.getCallerDisplayName(), | 
 |                             parcel.getCallerDisplayNamePresentation()); | 
 |                 } | 
 |                 // Set state after handle so that the client can identify the connection. | 
 |                 if (parcel.getState() == Connection.STATE_DISCONNECTED) { | 
 |                     connection.setDisconnected(parcel.getDisconnectCause()); | 
 |                 } else { | 
 |                     connection.setState(parcel.getState()); | 
 |                 } | 
 |                 List<RemoteConnection> conferenceable = new ArrayList<>(); | 
 |                 for (String confId : parcel.getConferenceableConnectionIds()) { | 
 |                     if (mConnectionById.containsKey(confId)) { | 
 |                         conferenceable.add(mConnectionById.get(confId)); | 
 |                     } | 
 |                 } | 
 |                 connection.setConferenceableConnections(conferenceable); | 
 |                 connection.setVideoState(parcel.getVideoState()); | 
 |                 if (connection.getState() == Connection.STATE_DISCONNECTED) { | 
 |                     // ... then, if it was created in a disconnected state, that indicates | 
 |                     // failure on the providing end, so immediately mark it destroyed | 
 |                     connection.setDestroyed(); | 
 |                 } | 
 |                 connection.setStatusHints(parcel.getStatusHints()); | 
 |                 connection.setIsVoipAudioMode(parcel.getIsVoipAudioMode()); | 
 |                 connection.setRingbackRequested(parcel.isRingbackRequested()); | 
 |                 connection.putExtras(parcel.getExtras()); | 
 |             } | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void handleCreateConferenceComplete( | 
 |                 String id, | 
 |                 ConnectionRequest request, | 
 |                 ParcelableConference parcel, | 
 |                 Session.Info info) { | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void setActive(String callId, Session.Info sessionInfo) { | 
 |             if (mConnectionById.containsKey(callId)) { | 
 |                 findConnectionForAction(callId, "setActive") | 
 |                         .setState(Connection.STATE_ACTIVE); | 
 |             } else { | 
 |                 findConferenceForAction(callId, "setActive") | 
 |                         .setState(Connection.STATE_ACTIVE); | 
 |             } | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void setRinging(String callId, Session.Info sessionInfo) { | 
 |             findConnectionForAction(callId, "setRinging") | 
 |                     .setState(Connection.STATE_RINGING); | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void setDialing(String callId, Session.Info sessionInfo) { | 
 |             findConnectionForAction(callId, "setDialing") | 
 |                     .setState(Connection.STATE_DIALING); | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void setPulling(String callId, Session.Info sessionInfo) { | 
 |             findConnectionForAction(callId, "setPulling") | 
 |                     .setState(Connection.STATE_PULLING_CALL); | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void setDisconnected(String callId, DisconnectCause disconnectCause, | 
 |                 Session.Info sessionInfo) { | 
 |             if (mConnectionById.containsKey(callId)) { | 
 |                 findConnectionForAction(callId, "setDisconnected") | 
 |                         .setDisconnected(disconnectCause); | 
 |             } else { | 
 |                 findConferenceForAction(callId, "setDisconnected") | 
 |                         .setDisconnected(disconnectCause); | 
 |             } | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void setOnHold(String callId, Session.Info sessionInfo) { | 
 |             if (mConnectionById.containsKey(callId)) { | 
 |                 findConnectionForAction(callId, "setOnHold") | 
 |                         .setState(Connection.STATE_HOLDING); | 
 |             } else { | 
 |                 findConferenceForAction(callId, "setOnHold") | 
 |                         .setState(Connection.STATE_HOLDING); | 
 |             } | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void setRingbackRequested(String callId, boolean ringing, Session.Info sessionInfo) { | 
 |             findConnectionForAction(callId, "setRingbackRequested") | 
 |                     .setRingbackRequested(ringing); | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void setConnectionCapabilities(String callId, int connectionCapabilities, | 
 |                 Session.Info sessionInfo) { | 
 |             if (mConnectionById.containsKey(callId)) { | 
 |                 findConnectionForAction(callId, "setConnectionCapabilities") | 
 |                         .setConnectionCapabilities(connectionCapabilities); | 
 |             } else { | 
 |                 findConferenceForAction(callId, "setConnectionCapabilities") | 
 |                         .setConnectionCapabilities(connectionCapabilities); | 
 |             } | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void setConnectionProperties(String callId, int connectionProperties, | 
 |                 Session.Info sessionInfo) { | 
 |             if (mConnectionById.containsKey(callId)) { | 
 |                 findConnectionForAction(callId, "setConnectionProperties") | 
 |                         .setConnectionProperties(connectionProperties); | 
 |             } else { | 
 |                 findConferenceForAction(callId, "setConnectionProperties") | 
 |                         .setConnectionProperties(connectionProperties); | 
 |             } | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void setIsConferenced(String callId, String conferenceCallId, | 
 |                 Session.Info sessionInfo) { | 
 |             // Note: callId should not be null; conferenceCallId may be null | 
 |             RemoteConnection connection = | 
 |                     findConnectionForAction(callId, "setIsConferenced"); | 
 |             if (connection != NULL_CONNECTION) { | 
 |                 if (conferenceCallId == null) { | 
 |                     // 'connection' is being split from its conference | 
 |                     if (connection.getConference() != null) { | 
 |                         connection.getConference().removeConnection(connection); | 
 |                     } | 
 |                 } else { | 
 |                     RemoteConference conference = | 
 |                             findConferenceForAction(conferenceCallId, "setIsConferenced"); | 
 |                     if (conference != NULL_CONFERENCE) { | 
 |                         conference.addConnection(connection); | 
 |                     } | 
 |                 } | 
 |             } | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void setConferenceMergeFailed(String callId, Session.Info sessionInfo) { | 
 |             // Nothing to do here. | 
 |             // The event has already been handled and there is no state to update | 
 |             // in the underlying connection or conference objects | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void onPhoneAccountChanged(String callId, PhoneAccountHandle pHandle, | 
 |                 Session.Info sessionInfo) { | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void onConnectionServiceFocusReleased(Session.Info sessionInfo) {} | 
 |  | 
 |         @Override | 
 |         public void addConferenceCall( | 
 |                 final String callId, ParcelableConference parcel, Session.Info sessionInfo) { | 
 |             RemoteConference conference = new RemoteConference(callId, | 
 |                     mOutgoingConnectionServiceRpc); | 
 |  | 
 |             for (String id : parcel.getConnectionIds()) { | 
 |                 RemoteConnection c = mConnectionById.get(id); | 
 |                 if (c != null) { | 
 |                     conference.addConnection(c); | 
 |                 } | 
 |             } | 
 |             // We used to skip adding empty conferences; however in the world of IMS conference | 
 |             // calls we need to add them to the remote connection service because they will always | 
 |             // start with no participants. | 
 |  | 
 |             conference.setState(parcel.getState()); | 
 |             conference.setConnectionCapabilities(parcel.getConnectionCapabilities()); | 
 |             conference.setConnectionProperties(parcel.getConnectionProperties()); | 
 |             conference.putExtras(parcel.getExtras()); | 
 |             mConferenceById.put(callId, conference); | 
 |  | 
 |             // Stash the original connection ID as it exists in the source ConnectionService. | 
 |             // Telecom will use this to avoid adding duplicates later. | 
 |             // See comments on Connection.EXTRA_ORIGINAL_CONNECTION_ID for more information. | 
 |             Bundle newExtras = new Bundle(); | 
 |             newExtras.putString(Connection.EXTRA_ORIGINAL_CONNECTION_ID, callId); | 
 |             // Track the fact this request was relayed through the remote connection service. | 
 |             newExtras.putParcelable(Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE, | 
 |                     parcel.getPhoneAccount()); | 
 |             conference.putExtras(newExtras); | 
 |  | 
 |             conference.registerCallback(new RemoteConference.Callback() { | 
 |                 @Override | 
 |                 public void onDestroyed(RemoteConference c) { | 
 |                     mConferenceById.remove(callId); | 
 |                     maybeDisconnectAdapter(); | 
 |                 } | 
 |             }); | 
 |  | 
 |             mOurConnectionServiceImpl.addRemoteConference(conference); | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void removeCall(String callId, Session.Info sessionInfo) { | 
 |             if (mConnectionById.containsKey(callId)) { | 
 |                 findConnectionForAction(callId, "removeCall") | 
 |                         .setDestroyed(); | 
 |             } else { | 
 |                 findConferenceForAction(callId, "removeCall") | 
 |                         .setDestroyed(); | 
 |             } | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void onPostDialWait(String callId, String remaining, Session.Info sessionInfo) { | 
 |             findConnectionForAction(callId, "onPostDialWait") | 
 |                     .setPostDialWait(remaining); | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void onPostDialChar(String callId, char nextChar, Session.Info sessionInfo) { | 
 |             findConnectionForAction(callId, "onPostDialChar") | 
 |                     .onPostDialChar(nextChar); | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void queryRemoteConnectionServices(RemoteServiceCallback callback, | 
 |                 String callingPackage, Session.Info sessionInfo) { | 
 |             // Not supported from remote connection service. | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void setVideoProvider(String callId, IVideoProvider videoProvider, | 
 |                 Session.Info sessionInfo) { | 
 |  | 
 |             String callingPackage = mOurConnectionServiceImpl.getApplicationContext() | 
 |                     .getOpPackageName(); | 
 |             int targetSdkVersion = mOurConnectionServiceImpl.getApplicationInfo().targetSdkVersion; | 
 |             RemoteConnection.VideoProvider remoteVideoProvider = null; | 
 |             if (videoProvider != null) { | 
 |                 remoteVideoProvider = new RemoteConnection.VideoProvider(videoProvider, | 
 |                         callingPackage, targetSdkVersion); | 
 |             } | 
 |             findConnectionForAction(callId, "setVideoProvider") | 
 |                     .setVideoProvider(remoteVideoProvider); | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void setVideoState(String callId, int videoState, Session.Info sessionInfo) { | 
 |             findConnectionForAction(callId, "setVideoState") | 
 |                     .setVideoState(videoState); | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void setIsVoipAudioMode(String callId, boolean isVoip, Session.Info sessionInfo) { | 
 |             findConnectionForAction(callId, "setIsVoipAudioMode") | 
 |                     .setIsVoipAudioMode(isVoip); | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void setStatusHints(String callId, StatusHints statusHints, | 
 |                 Session.Info sessionInfo) { | 
 |             findConnectionForAction(callId, "setStatusHints") | 
 |                     .setStatusHints(statusHints); | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void setAddress(String callId, Uri address, int presentation, | 
 |                 Session.Info sessionInfo) { | 
 |             findConnectionForAction(callId, "setAddress") | 
 |                     .setAddress(address, presentation); | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void setCallerDisplayName(String callId, String callerDisplayName, | 
 |                 int presentation, Session.Info sessionInfo) { | 
 |             findConnectionForAction(callId, "setCallerDisplayName") | 
 |                     .setCallerDisplayName(callerDisplayName, presentation); | 
 |         } | 
 |  | 
 |         @Override | 
 |         public IBinder asBinder() { | 
 |             throw new UnsupportedOperationException(); | 
 |         } | 
 |  | 
 |         @Override | 
 |         public final void setConferenceableConnections(String callId, | 
 |                 List<String> conferenceableConnectionIds, Session.Info sessionInfo) { | 
 |             List<RemoteConnection> conferenceable = new ArrayList<>(); | 
 |             for (String id : conferenceableConnectionIds) { | 
 |                 if (mConnectionById.containsKey(id)) { | 
 |                     conferenceable.add(mConnectionById.get(id)); | 
 |                 } | 
 |             } | 
 |  | 
 |             if (hasConnection(callId)) { | 
 |                 findConnectionForAction(callId, "setConferenceableConnections") | 
 |                         .setConferenceableConnections(conferenceable); | 
 |             } else { | 
 |                 findConferenceForAction(callId, "setConferenceableConnections") | 
 |                         .setConferenceableConnections(conferenceable); | 
 |             } | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void addExistingConnection(String callId, ParcelableConnection connection, | 
 |                 Session.Info sessionInfo) { | 
 |             Log.i(RemoteConnectionService.this, "addExistingConnection: callId=%s, conn=%s", callId, | 
 |                     connection); | 
 |             String callingPackage = mOurConnectionServiceImpl.getApplicationContext(). | 
 |                     getOpPackageName(); | 
 |             int callingTargetSdkVersion = mOurConnectionServiceImpl.getApplicationInfo() | 
 |                     .targetSdkVersion; | 
 |             RemoteConnection remoteConnection = new RemoteConnection(callId, | 
 |                     mOutgoingConnectionServiceRpc, connection, callingPackage, | 
 |                     callingTargetSdkVersion); | 
 |             // Track that it is via a remote connection. | 
 |             Bundle newExtras = new Bundle(); | 
 |             newExtras.putParcelable(Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE, | 
 |                     connection.getPhoneAccount()); | 
 |             if (connection.getParentCallId() != null) { | 
 |                 RemoteConference parentConf = mConferenceById.get(connection.getParentCallId()); | 
 |                 // If there is a parent being set, we need to stash the conference ID here. | 
 |                 // Telephony can add an existing connection while specifying a parent conference. | 
 |                 // There is no equivalent version of that operation as part of the remote connection | 
 |                 // API, so we will stash the pre-defined parent's ID in the extras.  When the | 
 |                 // connectionmanager copies over the extras from the remote connection to the | 
 |                 // actual one, it'll get passed to Telecom so that it can make the association. | 
 |                 if (parentConf != null) { | 
 |                     newExtras.putString(Connection.EXTRA_ADD_TO_CONFERENCE_ID, parentConf.getId()); | 
 |                     Log.i(this, "addExistingConnection: stash parent of %s as %s", | 
 |                             connection.getParentCallId(), parentConf.getId()); | 
 |                 } | 
 |             } | 
 |             remoteConnection.putExtras(newExtras); | 
 |             mConnectionById.put(callId, remoteConnection); | 
 |             remoteConnection.registerCallback(new RemoteConnection.Callback() { | 
 |                 @Override | 
 |                 public void onDestroyed(RemoteConnection connection) { | 
 |                     mConnectionById.remove(callId); | 
 |                     maybeDisconnectAdapter(); | 
 |                 } | 
 |             }); | 
 |             mOurConnectionServiceImpl.addRemoteExistingConnection(remoteConnection); | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void putExtras(String callId, Bundle extras, Session.Info sessionInfo) { | 
 |             if (hasConnection(callId)) { | 
 |                 findConnectionForAction(callId, "putExtras").putExtras(extras); | 
 |             } else { | 
 |                 findConferenceForAction(callId, "putExtras").putExtras(extras); | 
 |             } | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void removeExtras(String callId, List<String> keys, Session.Info sessionInfo) { | 
 |             if (hasConnection(callId)) { | 
 |                 findConnectionForAction(callId, "removeExtra").removeExtras(keys); | 
 |             } else { | 
 |                 findConferenceForAction(callId, "removeExtra").removeExtras(keys); | 
 |             } | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void setAudioRoute(String callId, int audioRoute, String bluetoothAddress, | 
 |                 Session.Info sessionInfo) { | 
 |             if (hasConnection(callId)) { | 
 |                 // TODO(3pcalls): handle this for remote connections. | 
 |                 // Likely we don't want to do anything since it doesn't make sense for self-managed | 
 |                 // connections to go through a connection mgr. | 
 |             } | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void onConnectionEvent(String callId, String event, Bundle extras, | 
 |                 Session.Info sessionInfo) { | 
 |             if (mConnectionById.containsKey(callId)) { | 
 |                 findConnectionForAction(callId, "onConnectionEvent").onConnectionEvent(event, | 
 |                         extras); | 
 |             } | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void onRttInitiationSuccess(String callId, Session.Info sessionInfo) | 
 |                 throws RemoteException { | 
 |             if (hasConnection(callId)) { | 
 |                 findConnectionForAction(callId, "onRttInitiationSuccess") | 
 |                         .onRttInitiationSuccess(); | 
 |             } else { | 
 |                 Log.w(this, "onRttInitiationSuccess called on a remote conference"); | 
 |             } | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void onRttInitiationFailure(String callId, int reason, Session.Info sessionInfo) | 
 |                 throws RemoteException { | 
 |             if (hasConnection(callId)) { | 
 |                 findConnectionForAction(callId, "onRttInitiationFailure") | 
 |                         .onRttInitiationFailure(reason); | 
 |             } else { | 
 |                 Log.w(this, "onRttInitiationFailure called on a remote conference"); | 
 |             } | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void onRttSessionRemotelyTerminated(String callId, Session.Info sessionInfo) | 
 |                 throws RemoteException { | 
 |             if (hasConnection(callId)) { | 
 |                 findConnectionForAction(callId, "onRttSessionRemotelyTerminated") | 
 |                         .onRttSessionRemotelyTerminated(); | 
 |             } else { | 
 |                 Log.w(this, "onRttSessionRemotelyTerminated called on a remote conference"); | 
 |             } | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void onRemoteRttRequest(String callId, Session.Info sessionInfo) | 
 |                 throws RemoteException { | 
 |             if (hasConnection(callId)) { | 
 |                 findConnectionForAction(callId, "onRemoteRttRequest") | 
 |                         .onRemoteRttRequest(); | 
 |             } else { | 
 |                 Log.w(this, "onRemoteRttRequest called on a remote conference"); | 
 |             } | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void resetConnectionTime(String callId, Session.Info sessionInfo) { | 
 |             // Do nothing | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void setConferenceState(String callId, boolean isConference, | 
 |                 Session.Info sessionInfo) { | 
 |             // Do nothing | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void setCallDirection(String callId, int direction, Session.Info sessionInfo) { | 
 |             // Do nothing | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void requestCallEndpointChange(String callId, CallEndpoint endpoint, | 
 |                 ResultReceiver callback, Session.Info sessionInfo) { | 
 |             // Do nothing | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void queryLocation(String callId, long timeoutMillis, String provider, | 
 |                 ResultReceiver callback, Session.Info sessionInfo) { | 
 |             // Do nothing | 
 |         } | 
 |     }; | 
 |  | 
 |     private final ConnectionServiceAdapterServant mServant = | 
 |             new ConnectionServiceAdapterServant(mServantDelegate); | 
 |  | 
 |     private final DeathRecipient mDeathRecipient = new DeathRecipient() { | 
 |         @Override | 
 |         public void binderDied() { | 
 |             for (RemoteConnection c : mConnectionById.values()) { | 
 |                 c.setDestroyed(); | 
 |             } | 
 |             for (RemoteConference c : mConferenceById.values()) { | 
 |                 c.setDestroyed(); | 
 |             } | 
 |             mConnectionById.clear(); | 
 |             mConferenceById.clear(); | 
 |             mPendingConnections.clear(); | 
 |             mOutgoingConnectionServiceRpc.asBinder().unlinkToDeath(mDeathRecipient, 0); | 
 |         } | 
 |     }; | 
 |  | 
 |     private final IConnectionService mOutgoingConnectionServiceRpc; | 
 |     private final ConnectionService mOurConnectionServiceImpl; | 
 |     private final Map<String, RemoteConnection> mConnectionById = new HashMap<>(); | 
 |     private final Map<String, RemoteConference> mConferenceById = new HashMap<>(); | 
 |     private final Set<RemoteConnection> mPendingConnections = new HashSet<>(); | 
 |  | 
 |     RemoteConnectionService( | 
 |             IConnectionService outgoingConnectionServiceRpc, | 
 |             ConnectionService ourConnectionServiceImpl) throws RemoteException { | 
 |         mOutgoingConnectionServiceRpc = outgoingConnectionServiceRpc; | 
 |         mOutgoingConnectionServiceRpc.asBinder().linkToDeath(mDeathRecipient, 0); | 
 |         mOurConnectionServiceImpl = ourConnectionServiceImpl; | 
 |     } | 
 |  | 
 |     @Override | 
 |     public String toString() { | 
 |         return "[RemoteCS - " + mOutgoingConnectionServiceRpc.asBinder().toString() + "]"; | 
 |     } | 
 |  | 
 |     final RemoteConnection createRemoteConnection( | 
 |             PhoneAccountHandle connectionManagerPhoneAccount, | 
 |             ConnectionRequest request, | 
 |             boolean isIncoming) { | 
 |         final String id = UUID.randomUUID().toString(); | 
 |         Bundle extras = new Bundle(); | 
 |         if (request.getExtras() != null) { | 
 |             extras.putAll(request.getExtras()); | 
 |         } | 
 |         // We will set the package name for the originator of the remote request; this lets the | 
 |         // receiving ConnectionService know that the request originated from a remote connection | 
 |         // service so that it can provide tracking information for Telecom. | 
 |         extras.putString(Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME, | 
 |                 mOurConnectionServiceImpl.getApplicationContext().getOpPackageName()); | 
 |  | 
 |         final ConnectionRequest newRequest = new ConnectionRequest.Builder() | 
 |                 .setAccountHandle(request.getAccountHandle()) | 
 |                 .setAddress(request.getAddress()) | 
 |                 .setExtras(extras) | 
 |                 .setVideoState(request.getVideoState()) | 
 |                 .setRttPipeFromInCall(request.getRttPipeFromInCall()) | 
 |                 .setRttPipeToInCall(request.getRttPipeToInCall()) | 
 |                 .build(); | 
 |         try { | 
 |             if (mConnectionById.isEmpty()) { | 
 |                 mOutgoingConnectionServiceRpc.addConnectionServiceAdapter(mServant.getStub(), | 
 |                         null /*Session.Info*/); | 
 |             } | 
 |             RemoteConnection connection = | 
 |                     new RemoteConnection(id, mOutgoingConnectionServiceRpc, newRequest); | 
 |             mPendingConnections.add(connection); | 
 |             mConnectionById.put(id, connection); | 
 |             mOutgoingConnectionServiceRpc.createConnection( | 
 |                     connectionManagerPhoneAccount, | 
 |                     id, | 
 |                     newRequest, | 
 |                     isIncoming, | 
 |                     false /* isUnknownCall */, | 
 |                     null /*Session.info*/); | 
 |             connection.registerCallback(new RemoteConnection.Callback() { | 
 |                 @Override | 
 |                 public void onDestroyed(RemoteConnection connection) { | 
 |                     mConnectionById.remove(id); | 
 |                     maybeDisconnectAdapter(); | 
 |                 } | 
 |             }); | 
 |             return connection; | 
 |         } catch (RemoteException e) { | 
 |             return RemoteConnection.failure( | 
 |                     new DisconnectCause(DisconnectCause.ERROR, e.toString())); | 
 |         } | 
 |     } | 
 |  | 
 |     RemoteConference createRemoteConference( | 
 |             PhoneAccountHandle connectionManagerPhoneAccount, | 
 |             ConnectionRequest request, | 
 |             boolean isIncoming) { | 
 |         final String id = UUID.randomUUID().toString(); | 
 |         try { | 
 |             if (mConferenceById.isEmpty()) { | 
 |                 mOutgoingConnectionServiceRpc.addConnectionServiceAdapter(mServant.getStub(), | 
 |                         null /*Session.Info*/); | 
 |             } | 
 |             RemoteConference conference = new RemoteConference(id, mOutgoingConnectionServiceRpc); | 
 |             mOutgoingConnectionServiceRpc.createConference(connectionManagerPhoneAccount, | 
 |                     id, | 
 |                     request, | 
 |                     isIncoming, | 
 |                     false /* isUnknownCall */, | 
 |                     null /*Session.info*/); | 
 |             conference.registerCallback(new RemoteConference.Callback() { | 
 |                 @Override | 
 |                 public void onDestroyed(RemoteConference conference) { | 
 |                     mConferenceById.remove(id); | 
 |                     maybeDisconnectAdapter(); | 
 |                 } | 
 |             }); | 
 |             conference.putExtras(request.getExtras()); | 
 |             return conference; | 
 |         } catch (RemoteException e) { | 
 |             return RemoteConference.failure( | 
 |                     new DisconnectCause(DisconnectCause.ERROR, e.toString())); | 
 |         } | 
 |     } | 
 |  | 
 |     private boolean hasConnection(String callId) { | 
 |         return mConnectionById.containsKey(callId); | 
 |     } | 
 |  | 
 |     private RemoteConnection findConnectionForAction( | 
 |             String callId, String action) { | 
 |         if (mConnectionById.containsKey(callId)) { | 
 |             return mConnectionById.get(callId); | 
 |         } | 
 |         Log.w(this, "%s - Cannot find Connection %s", action, callId); | 
 |         return NULL_CONNECTION; | 
 |     } | 
 |  | 
 |     private RemoteConference findConferenceForAction( | 
 |             String callId, String action) { | 
 |         if (mConferenceById.containsKey(callId)) { | 
 |             return mConferenceById.get(callId); | 
 |         } | 
 |         Log.w(this, "%s - Cannot find Conference %s", action, callId); | 
 |         return NULL_CONFERENCE; | 
 |     } | 
 |  | 
 |     private void maybeDisconnectAdapter() { | 
 |         if (mConnectionById.isEmpty() && mConferenceById.isEmpty()) { | 
 |             try { | 
 |                 mOutgoingConnectionServiceRpc.removeConnectionServiceAdapter(mServant.getStub(), | 
 |                         null /*Session.info*/); | 
 |             } catch (RemoteException e) { | 
 |             } | 
 |         } | 
 |     } | 
 | } |