Files
frameworks_base/telecomm/java/android/telecom/RemoteConnectionService.java
Tyler Gunn 38f1b7d112 Ensure start of call properties are propagated to RemoteConnection.
Some properties were not being propagated to a RemoteConnection when
a Connection is first created.  This can cause problems where the
Connection Manager is not aware of these property changes, especially if
they never change again during the lifetime of a call.

The extras set when a Connection is first created using the
RemoteConnectionService API would not be propagated to the
RemoteConnection.  This means that Telephony RAT reporting would never
happen if the radio technology never changes during a call.

Bug: 72811636
Test: Manual
Change-Id: Ia523cee477a39c221953cda68c29579cb5f6ed76
Merged-In: 06a96eab13
(cherry picked from commit 06a96eab13)
2018-03-08 17:21:47 +00:00

584 lines
24 KiB
Java

/*
* 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.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.Map;
import java.util.Set;
import java.util.List;
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 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);
}
}
if (conference.getConnections().size() == 0) {
// A conference was created, but none of its connections are ones that have been
// created by, and therefore being tracked by, this remote connection service. It
// is of no interest to us.
Log.d(this, "addConferenceCall - skipping");
return;
}
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);
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,
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) {
String callingPackage = mOurConnectionServiceImpl.getApplicationContext().
getOpPackageName();
int callingTargetSdkVersion = mOurConnectionServiceImpl.getApplicationInfo()
.targetSdkVersion;
RemoteConnection remoteConnection = new RemoteConnection(callId,
mOutgoingConnectionServiceRpc, connection, callingPackage,
callingTargetSdkVersion);
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");
}
}
};
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();
final ConnectionRequest newRequest = new ConnectionRequest.Builder()
.setAccountHandle(request.getAccountHandle())
.setAddress(request.getAddress())
.setExtras(request.getExtras())
.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()));
}
}
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) {
}
}
}
}