2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android.telecomm;
19 import android.app.PendingIntent;
20 import android.net.Uri;
21 import android.os.IBinder;
22 import android.os.IBinder.DeathRecipient;
23 import android.os.RemoteException;
24 import android.telephony.DisconnectCause;
26 import com.android.internal.telecomm.IConnectionService;
27 import com.android.internal.telecomm.IConnectionServiceAdapter;
28 import com.android.internal.telecomm.IVideoProvider;
29 import com.android.internal.telecomm.RemoteServiceCallback;
31 import java.util.ArrayList;
32 import java.util.HashMap;
33 import java.util.HashSet;
36 import java.util.List;
37 import java.util.UUID;
40 * Remote connection service which other connection services can use to place calls on their behalf.
44 final class RemoteConnectionService {
46 private static final RemoteConnection NULL_CONNECTION =
47 new RemoteConnection("NULL", null, null);
49 private static final RemoteConference NULL_CONFERENCE =
50 new RemoteConference("NULL", null);
52 private final IConnectionServiceAdapter mServantDelegate = new IConnectionServiceAdapter() {
54 public void handleCreateConnectionComplete(
56 ConnectionRequest request,
57 ParcelableConnection parcel) {
58 RemoteConnection connection =
59 findConnectionForAction(id, "handleCreateConnectionSuccessful");
60 if (connection != NULL_CONNECTION && mPendingConnections.contains(connection)) {
61 mPendingConnections.remove(connection);
62 // Unconditionally initialize the connection ...
63 connection.setState(parcel.getState());
64 connection.setCallCapabilities(parcel.getCapabilities());
66 parcel.getHandle(), parcel.getHandlePresentation());
67 connection.setCallerDisplayName(
68 parcel.getCallerDisplayName(),
69 parcel.getCallerDisplayNamePresentation());
70 List<RemoteConnection> conferenceable = new ArrayList<>();
71 for (String confId : parcel.getConferenceableConnectionIds()) {
72 if (mConnectionById.containsKey(confId)) {
73 conferenceable.add(mConnectionById.get(confId));
76 connection.setConferenceableConnections(conferenceable);
77 // TODO: Do we need to support video providers for remote connections?
78 if (connection.getState() == Connection.STATE_DISCONNECTED) {
79 // ... then, if it was created in a disconnected state, that indicates
80 // failure on the providing end, so immediately mark it destroyed
81 connection.setDestroyed();
87 public void setActive(String callId) {
88 if (mConnectionById.containsKey(callId)) {
89 findConnectionForAction(callId, "setActive")
90 .setState(Connection.STATE_ACTIVE);
92 findConferenceForAction(callId, "setActive")
93 .setState(Connection.STATE_ACTIVE);
98 public void setRinging(String callId) {
99 findConnectionForAction(callId, "setRinging")
100 .setState(Connection.STATE_RINGING);
104 public void setDialing(String callId) {
105 findConnectionForAction(callId, "setDialing")
106 .setState(Connection.STATE_DIALING);
110 public void setDisconnected(String callId, int disconnectCause,
111 String disconnectMessage) {
112 if (mConnectionById.containsKey(callId)) {
113 findConnectionForAction(callId, "setDisconnected")
114 .setDisconnected(disconnectCause, disconnectMessage);
116 findConferenceForAction(callId, "setDisconnected")
117 .setDisconnected(disconnectCause, disconnectMessage);
122 public void setOnHold(String callId) {
123 if (mConnectionById.containsKey(callId)) {
124 findConnectionForAction(callId, "setOnHold")
125 .setState(Connection.STATE_HOLDING);
127 findConferenceForAction(callId, "setOnHold")
128 .setState(Connection.STATE_HOLDING);
133 public void setRequestingRingback(String callId, boolean ringing) {
134 findConnectionForAction(callId, "setRequestingRingback")
135 .setRequestingRingback(ringing);
139 public void setCallCapabilities(String callId, int callCapabilities) {
140 if (mConnectionById.containsKey(callId)) {
141 findConnectionForAction(callId, "setCallCapabilities")
142 .setCallCapabilities(callCapabilities);
144 findConferenceForAction(callId, "setCallCapabilities")
145 .setCallCapabilities(callCapabilities);
150 public void setIsConferenced(String callId, String conferenceCallId) {
151 // Note: callId should not be null; conferenceCallId may be null
152 RemoteConnection connection =
153 findConnectionForAction(callId, "setIsConferenced");
154 if (connection != NULL_CONNECTION) {
155 if (conferenceCallId == null) {
156 // 'connection' is being split from its conference
157 if (connection.getConference() != null) {
158 connection.getConference().removeConnection(connection);
161 RemoteConference conference =
162 findConferenceForAction(conferenceCallId, "setIsConferenced");
163 if (conference != NULL_CONFERENCE) {
164 conference.addConnection(connection);
171 public void addConferenceCall(
173 ParcelableConference parcel) {
174 RemoteConference conference = new RemoteConference(callId,
175 mOutgoingConnectionServiceRpc);
177 for (String id : parcel.getConnectionIds()) {
178 RemoteConnection c = mConnectionById.get(id);
180 conference.addConnection(c);
184 if (conference.getConnections().size() == 0) {
185 // A conference was created, but none of its connections are ones that have been
186 // created by, and therefore being tracked by, this remote connection service. It
187 // is of no interest to us.
191 conference.setState(parcel.getState());
192 conference.setCallCapabilities(parcel.getCapabilities());
193 mConferenceById.put(callId, conference);
194 conference.addCallback(new RemoteConference.Callback() {
196 public void onDestroyed(RemoteConference c) {
197 mConferenceById.remove(callId);
198 maybeDisconnectAdapter();
202 mOurConnectionServiceImpl.addRemoteConference(conference);
206 public void removeCall(String callId) {
207 if (mConnectionById.containsKey(callId)) {
208 findConnectionForAction(callId, "removeCall")
211 findConferenceForAction(callId, "removeCall")
217 public void onPostDialWait(String callId, String remaining) {
218 findConnectionForAction(callId, "onPostDialWait")
219 .setPostDialWait(remaining);
223 public void queryRemoteConnectionServices(RemoteServiceCallback callback) {
224 // Not supported from remote connection service.
228 public void setVideoProvider(String callId, IVideoProvider videoProvider) {
229 // not supported for remote connections.
233 public void setVideoState(String callId, int videoState) {
234 findConnectionForAction(callId, "setVideoState")
235 .setVideoState(videoState);
239 public void setAudioModeIsVoip(String callId, boolean isVoip) {
240 findConnectionForAction(callId, "setAudioModeIsVoip")
241 .setAudioModeIsVoip(isVoip);
245 public void setStatusHints(String callId, StatusHints statusHints) {
246 findConnectionForAction(callId, "setStatusHints")
247 .setStatusHints(statusHints);
251 public void setHandle(String callId, Uri handle, int presentation) {
252 findConnectionForAction(callId, "setHandle")
253 .setHandle(handle, presentation);
257 public void setCallerDisplayName(String callId, String callerDisplayName,
259 findConnectionForAction(callId, "setCallerDisplayName")
260 .setCallerDisplayName(callerDisplayName, presentation);
264 public IBinder asBinder() {
265 throw new UnsupportedOperationException();
269 public final void setConferenceableConnections(
270 String callId, List<String> conferenceableConnectionIds) {
271 List<RemoteConnection> conferenceable = new ArrayList<>();
272 for (String id : conferenceableConnectionIds) {
273 if (mConnectionById.containsKey(id)) {
274 conferenceable.add(mConnectionById.get(id));
278 findConnectionForAction(callId, "setConferenceableConnections")
279 .setConferenceableConnections(conferenceable);
283 private final ConnectionServiceAdapterServant mServant =
284 new ConnectionServiceAdapterServant(mServantDelegate);
286 private final DeathRecipient mDeathRecipient = new DeathRecipient() {
288 public void binderDied() {
289 for (RemoteConnection c : mConnectionById.values()) {
292 for (RemoteConference c : mConferenceById.values()) {
295 mConnectionById.clear();
296 mConferenceById.clear();
297 mPendingConnections.clear();
298 mOutgoingConnectionServiceRpc.asBinder().unlinkToDeath(mDeathRecipient, 0);
302 private final IConnectionService mOutgoingConnectionServiceRpc;
303 private final ConnectionService mOurConnectionServiceImpl;
304 private final Map<String, RemoteConnection> mConnectionById = new HashMap<>();
305 private final Map<String, RemoteConference> mConferenceById = new HashMap<>();
306 private final Set<RemoteConnection> mPendingConnections = new HashSet<>();
308 RemoteConnectionService(
309 IConnectionService outgoingConnectionServiceRpc,
310 ConnectionService ourConnectionServiceImpl) throws RemoteException {
311 mOutgoingConnectionServiceRpc = outgoingConnectionServiceRpc;
312 mOutgoingConnectionServiceRpc.asBinder().linkToDeath(mDeathRecipient, 0);
313 mOurConnectionServiceImpl = ourConnectionServiceImpl;
317 public String toString() {
318 return "[RemoteCS - " + mOutgoingConnectionServiceRpc.asBinder().toString() + "]";
321 final RemoteConnection createRemoteConnection(
322 PhoneAccountHandle connectionManagerPhoneAccount,
323 ConnectionRequest request,
324 boolean isIncoming) {
325 final String id = UUID.randomUUID().toString();
326 final ConnectionRequest newRequest = new ConnectionRequest(
327 request.getAccountHandle(),
329 request.getHandlePresentation(),
331 request.getVideoState());
333 if (mConnectionById.isEmpty()) {
334 mOutgoingConnectionServiceRpc.addConnectionServiceAdapter(mServant.getStub());
336 RemoteConnection connection =
337 new RemoteConnection(id, mOutgoingConnectionServiceRpc, newRequest);
338 mPendingConnections.add(connection);
339 mConnectionById.put(id, connection);
340 mOutgoingConnectionServiceRpc.createConnection(
341 connectionManagerPhoneAccount,
345 connection.addListener(new RemoteConnection.Listener() {
347 public void onDestroyed(RemoteConnection connection) {
348 mConnectionById.remove(id);
349 maybeDisconnectAdapter();
353 } catch (RemoteException e) {
354 return RemoteConnection
355 .failure(DisconnectCause.ERROR_UNSPECIFIED, e.toString());
359 private RemoteConnection findConnectionForAction(
360 String callId, String action) {
361 if (mConnectionById.containsKey(callId)) {
362 return mConnectionById.get(callId);
364 Log.w(this, "%s - Cannot find Connection %s", action, callId);
365 return NULL_CONNECTION;
368 private RemoteConference findConferenceForAction(
369 String callId, String action) {
370 if (mConferenceById.containsKey(callId)) {
371 return mConferenceById.get(callId);
373 Log.w(this, "%s - Cannot find Conference %s", action, callId);
374 return NULL_CONFERENCE;
377 private void maybeDisconnectAdapter() {
378 if (mConnectionById.isEmpty() && mConferenceById.isEmpty()) {
380 mOutgoingConnectionServiceRpc.removeConnectionServiceAdapter(mServant.getStub());
381 } catch (RemoteException e) {