Refactor ConnectionService API so it has only one "completed" callback, and connection state and failure codes indicates what happened. Previous design where we had separate callbacks for failure, cancellation and success was error prone because it was easy to forget to implement one of them. Bug: 16993846 Bug: 17070939 Change-Id: I84bf5d041cf78193ccf80db201b08db3b7014830
287 lines
11 KiB
Java
287 lines
11 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.telecomm;
|
|
|
|
import android.app.PendingIntent;
|
|
import android.net.Uri;
|
|
import android.os.IBinder;
|
|
import android.os.IBinder.DeathRecipient;
|
|
import android.os.RemoteException;
|
|
import android.telephony.DisconnectCause;
|
|
|
|
import com.android.internal.telecomm.IConnectionService;
|
|
import com.android.internal.telecomm.IConnectionServiceAdapter;
|
|
import com.android.internal.telecomm.IVideoProvider;
|
|
import com.android.internal.telecomm.RemoteServiceCallback;
|
|
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.Collections;
|
|
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 {
|
|
|
|
private static final RemoteConnection
|
|
NULL_CONNECTION = new RemoteConnection("NULL", null, null);
|
|
|
|
private final IConnectionServiceAdapter mServantDelegate = new IConnectionServiceAdapter() {
|
|
@Override
|
|
public void handleCreateConnectionComplete(
|
|
String id,
|
|
ConnectionRequest request,
|
|
ParcelableConnection parcel) {
|
|
RemoteConnection connection =
|
|
findConnectionForAction(id, "handleCreateConnectionSuccessful");
|
|
if (connection != NULL_CONNECTION && mPendingConnections.contains(connection)) {
|
|
mPendingConnections.remove(connection);
|
|
// Unconditionally initialize the connection ...
|
|
connection.setState(parcel.getState());
|
|
connection.setCallCapabilities(parcel.getCapabilities());
|
|
connection.setHandle(
|
|
parcel.getHandle(), parcel.getHandlePresentation());
|
|
connection.setCallerDisplayName(
|
|
parcel.getCallerDisplayName(),
|
|
parcel.getCallerDisplayNamePresentation());
|
|
// TODO: Do we need to support video providers for remote connections?
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setActive(String callId) {
|
|
findConnectionForAction(callId, "setActive")
|
|
.setState(Connection.STATE_ACTIVE);
|
|
}
|
|
|
|
@Override
|
|
public void setRinging(String callId) {
|
|
findConnectionForAction(callId, "setRinging")
|
|
.setState(Connection.STATE_RINGING);
|
|
}
|
|
|
|
@Override
|
|
public void setDialing(String callId) {
|
|
findConnectionForAction(callId, "setDialing")
|
|
.setState(Connection.STATE_DIALING);
|
|
}
|
|
|
|
@Override
|
|
public void setDisconnected(String callId, int disconnectCause,
|
|
String disconnectMessage) {
|
|
findConnectionForAction(callId, "setDisconnected")
|
|
.setDisconnected(disconnectCause, disconnectMessage);
|
|
}
|
|
|
|
@Override
|
|
public void setOnHold(String callId) {
|
|
findConnectionForAction(callId, "setOnHold")
|
|
.setState(Connection.STATE_HOLDING);
|
|
}
|
|
|
|
@Override
|
|
public void setRequestingRingback(String callId, boolean ringing) {
|
|
findConnectionForAction(callId, "setRequestingRingback")
|
|
.setRequestingRingback(ringing);
|
|
}
|
|
|
|
@Override
|
|
public void setCallCapabilities(String callId, int callCapabilities) {
|
|
findConnectionForAction("callId", "setCallCapabilities")
|
|
.setCallCapabilities(callCapabilities);
|
|
}
|
|
|
|
@Override
|
|
public void setIsConferenced(String callId, String conferenceCallId) {
|
|
// not supported for remote connections.
|
|
}
|
|
|
|
@Override
|
|
public void addConferenceCall(String callId, ParcelableConference parcelableConference) {
|
|
// not supported for remote connections.
|
|
}
|
|
|
|
@Override
|
|
public void removeCall(String callId) {
|
|
findConnectionForAction(callId, "removeCall")
|
|
.setDestroyed();
|
|
}
|
|
|
|
@Override
|
|
public void onPostDialWait(String callId, String remaining) {
|
|
findConnectionForAction(callId, "onPostDialWait")
|
|
.setPostDialWait(remaining);
|
|
}
|
|
|
|
@Override
|
|
public void queryRemoteConnectionServices(RemoteServiceCallback callback) {
|
|
// Not supported from remote connection service.
|
|
}
|
|
|
|
@Override
|
|
public void setVideoProvider(String callId, IVideoProvider videoProvider) {
|
|
// not supported for remote connections.
|
|
}
|
|
|
|
@Override
|
|
public void setVideoState(String callId, int videoState) {
|
|
findConnectionForAction(callId, "setVideoState")
|
|
.setVideoState(videoState);
|
|
}
|
|
|
|
@Override
|
|
public void setAudioModeIsVoip(String callId, boolean isVoip) {
|
|
findConnectionForAction(callId, "setAudioModeIsVoip")
|
|
.setAudioModeIsVoip(isVoip);
|
|
}
|
|
|
|
@Override
|
|
public void setStatusHints(String callId, StatusHints statusHints) {
|
|
findConnectionForAction(callId, "setStatusHints")
|
|
.setStatusHints(statusHints);
|
|
}
|
|
|
|
@Override
|
|
public void setHandle(String callId, Uri handle, int presentation) {
|
|
findConnectionForAction(callId, "setHandle")
|
|
.setHandle(handle, presentation);
|
|
}
|
|
|
|
@Override
|
|
public void setCallerDisplayName(String callId, String callerDisplayName,
|
|
int presentation) {
|
|
findConnectionForAction(callId, "setCallerDisplayName")
|
|
.setCallerDisplayName(callerDisplayName, presentation);
|
|
}
|
|
|
|
@Override
|
|
public void startActivityFromInCall(String callId, PendingIntent intent) {
|
|
findConnectionForAction(callId, "startActivityFromInCall")
|
|
.startActivityFromInCall(intent);
|
|
}
|
|
|
|
@Override
|
|
public IBinder asBinder() {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
@Override
|
|
public final void setConferenceableConnections(
|
|
String callId, List<String> conferenceableConnectionIds) {
|
|
|
|
// TODO: When we support more than 1 remote connection, this should
|
|
// loop through the incoming list of connection IDs and acquire the list
|
|
// of remote connections which correspond to the IDs. That list should
|
|
// be set onto the remote connections.
|
|
findConnectionForAction(callId, "setConferenceableConnections")
|
|
.setConferenceableConnections(Collections.<RemoteConnection>emptyList());
|
|
}
|
|
};
|
|
|
|
private final ConnectionServiceAdapterServant mServant =
|
|
new ConnectionServiceAdapterServant(mServantDelegate);
|
|
|
|
private final DeathRecipient mDeathRecipient = new DeathRecipient() {
|
|
@Override
|
|
public void binderDied() {
|
|
for (RemoteConnection c : mConnectionById.values()) {
|
|
c.setDestroyed();
|
|
}
|
|
mConnectionById.clear();
|
|
mPendingConnections.clear();
|
|
mConnectionService.asBinder().unlinkToDeath(mDeathRecipient, 0);
|
|
}
|
|
};
|
|
|
|
private final IConnectionService mConnectionService;
|
|
private final Map<String, RemoteConnection> mConnectionById = new HashMap<>();
|
|
private final Set<RemoteConnection> mPendingConnections = new HashSet<>();
|
|
|
|
RemoteConnectionService(IConnectionService connectionService) throws RemoteException {
|
|
mConnectionService = connectionService;
|
|
mConnectionService.asBinder().linkToDeath(mDeathRecipient, 0);
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "[RemoteCS - " + mConnectionService.asBinder().toString() + "]";
|
|
}
|
|
|
|
final RemoteConnection createRemoteConnection(
|
|
PhoneAccountHandle connectionManagerPhoneAccount,
|
|
ConnectionRequest request,
|
|
boolean isIncoming) {
|
|
final String id = UUID.randomUUID().toString();
|
|
final ConnectionRequest newRequest = new ConnectionRequest(
|
|
request.getAccountHandle(),
|
|
request.getHandle(),
|
|
request.getHandlePresentation(),
|
|
request.getExtras(),
|
|
request.getVideoState());
|
|
try {
|
|
if (mConnectionById.isEmpty()) {
|
|
mConnectionService.addConnectionServiceAdapter(mServant.getStub());
|
|
}
|
|
RemoteConnection connection =
|
|
new RemoteConnection(id, mConnectionService, newRequest);
|
|
mPendingConnections.add(connection);
|
|
mConnectionById.put(id, connection);
|
|
mConnectionService.createConnection(
|
|
connectionManagerPhoneAccount,
|
|
id,
|
|
newRequest,
|
|
isIncoming);
|
|
connection.addListener(new RemoteConnection.Listener() {
|
|
@Override
|
|
public void onDestroyed(RemoteConnection connection) {
|
|
mConnectionById.remove(id);
|
|
if (mConnectionById.isEmpty()) {
|
|
try {
|
|
mConnectionService.removeConnectionServiceAdapter(mServant.getStub());
|
|
} catch (RemoteException e) {
|
|
}
|
|
}
|
|
}
|
|
});
|
|
return connection;
|
|
} catch (RemoteException e) {
|
|
return RemoteConnection
|
|
.failure(DisconnectCause.ERROR_UNSPECIFIED, e.toString());
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|