• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 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 
17 package android.telecom;
18 
19 import android.net.Uri;
20 import android.os.Bundle;
21 import android.os.IBinder;
22 import android.os.IBinder.DeathRecipient;
23 import android.os.RemoteException;
24 
25 import com.android.internal.telecom.IConnectionService;
26 import com.android.internal.telecom.IConnectionServiceAdapter;
27 import com.android.internal.telecom.IVideoProvider;
28 import com.android.internal.telecom.RemoteServiceCallback;
29 
30 import java.util.ArrayList;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.Map;
34 import java.util.Set;
35 import java.util.List;
36 import java.util.UUID;
37 
38 /**
39  * Remote connection service which other connection services can use to place calls on their behalf.
40  *
41  * @hide
42  */
43 final class RemoteConnectionService {
44 
45     // Note: Casting null to avoid ambiguous constructor reference.
46     private static final RemoteConnection NULL_CONNECTION =
47             new RemoteConnection("NULL", null, (ConnectionRequest) null);
48 
49     private static final RemoteConference NULL_CONFERENCE =
50             new RemoteConference("NULL", null);
51 
52     private final IConnectionServiceAdapter mServantDelegate = new IConnectionServiceAdapter() {
53         @Override
54         public void handleCreateConnectionComplete(
55                 String id,
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.setConnectionCapabilities(parcel.getConnectionCapabilities());
64                 if (parcel.getHandle() != null
65                     || parcel.getState() != Connection.STATE_DISCONNECTED) {
66                     connection.setAddress(parcel.getHandle(), parcel.getHandlePresentation());
67                 }
68                 if (parcel.getCallerDisplayName() != null
69                     || parcel.getState() != Connection.STATE_DISCONNECTED) {
70                     connection.setCallerDisplayName(
71                             parcel.getCallerDisplayName(),
72                             parcel.getCallerDisplayNamePresentation());
73                 }
74                 // Set state after handle so that the client can identify the connection.
75                 if (parcel.getState() == Connection.STATE_DISCONNECTED) {
76                     connection.setDisconnected(parcel.getDisconnectCause());
77                 } else {
78                     connection.setState(parcel.getState());
79                 }
80                 List<RemoteConnection> conferenceable = new ArrayList<>();
81                 for (String confId : parcel.getConferenceableConnectionIds()) {
82                     if (mConnectionById.containsKey(confId)) {
83                         conferenceable.add(mConnectionById.get(confId));
84                     }
85                 }
86                 connection.setConferenceableConnections(conferenceable);
87                 connection.setVideoState(parcel.getVideoState());
88                 if (connection.getState() == Connection.STATE_DISCONNECTED) {
89                     // ... then, if it was created in a disconnected state, that indicates
90                     // failure on the providing end, so immediately mark it destroyed
91                     connection.setDestroyed();
92                 }
93             }
94         }
95 
96         @Override
97         public void setActive(String callId) {
98             if (mConnectionById.containsKey(callId)) {
99                 findConnectionForAction(callId, "setActive")
100                         .setState(Connection.STATE_ACTIVE);
101             } else {
102                 findConferenceForAction(callId, "setActive")
103                         .setState(Connection.STATE_ACTIVE);
104             }
105         }
106 
107         @Override
108         public void setRinging(String callId) {
109             findConnectionForAction(callId, "setRinging")
110                     .setState(Connection.STATE_RINGING);
111         }
112 
113         @Override
114         public void setDialing(String callId) {
115             findConnectionForAction(callId, "setDialing")
116                     .setState(Connection.STATE_DIALING);
117         }
118 
119         @Override
120         public void setDisconnected(String callId, DisconnectCause disconnectCause) {
121             if (mConnectionById.containsKey(callId)) {
122                 findConnectionForAction(callId, "setDisconnected")
123                         .setDisconnected(disconnectCause);
124             } else {
125                 findConferenceForAction(callId, "setDisconnected")
126                         .setDisconnected(disconnectCause);
127             }
128         }
129 
130         @Override
131         public void setOnHold(String callId) {
132             if (mConnectionById.containsKey(callId)) {
133                 findConnectionForAction(callId, "setOnHold")
134                         .setState(Connection.STATE_HOLDING);
135             } else {
136                 findConferenceForAction(callId, "setOnHold")
137                         .setState(Connection.STATE_HOLDING);
138             }
139         }
140 
141         @Override
142         public void setRingbackRequested(String callId, boolean ringing) {
143             findConnectionForAction(callId, "setRingbackRequested")
144                     .setRingbackRequested(ringing);
145         }
146 
147         @Override
148         public void setConnectionCapabilities(String callId, int connectionCapabilities) {
149             if (mConnectionById.containsKey(callId)) {
150                 findConnectionForAction(callId, "setConnectionCapabilities")
151                         .setConnectionCapabilities(connectionCapabilities);
152             } else {
153                 findConferenceForAction(callId, "setConnectionCapabilities")
154                         .setConnectionCapabilities(connectionCapabilities);
155             }
156         }
157 
158         @Override
159         public void setIsConferenced(String callId, String conferenceCallId) {
160             // Note: callId should not be null; conferenceCallId may be null
161             RemoteConnection connection =
162                     findConnectionForAction(callId, "setIsConferenced");
163             if (connection != NULL_CONNECTION) {
164                 if (conferenceCallId == null) {
165                     // 'connection' is being split from its conference
166                     if (connection.getConference() != null) {
167                         connection.getConference().removeConnection(connection);
168                     }
169                 } else {
170                     RemoteConference conference =
171                             findConferenceForAction(conferenceCallId, "setIsConferenced");
172                     if (conference != NULL_CONFERENCE) {
173                         conference.addConnection(connection);
174                     }
175                 }
176             }
177         }
178 
179         @Override
180         public void setConferenceMergeFailed(String callId) {
181             // Nothing to do here.
182             // The event has already been handled and there is no state to update
183             // in the underlying connection or conference objects
184         }
185 
186         @Override
187         public void addConferenceCall(
188                 final String callId,
189                 ParcelableConference parcel) {
190             RemoteConference conference = new RemoteConference(callId,
191                     mOutgoingConnectionServiceRpc);
192 
193             for (String id : parcel.getConnectionIds()) {
194                 RemoteConnection c = mConnectionById.get(id);
195                 if (c != null) {
196                     conference.addConnection(c);
197                 }
198             }
199 
200             if (conference.getConnections().size() == 0) {
201                 // A conference was created, but none of its connections are ones that have been
202                 // created by, and therefore being tracked by, this remote connection service. It
203                 // is of no interest to us.
204                 return;
205             }
206 
207             conference.setState(parcel.getState());
208             conference.setConnectionCapabilities(parcel.getConnectionCapabilities());
209             mConferenceById.put(callId, conference);
210             conference.registerCallback(new RemoteConference.Callback() {
211                 @Override
212                 public void onDestroyed(RemoteConference c) {
213                     mConferenceById.remove(callId);
214                     maybeDisconnectAdapter();
215                 }
216             });
217 
218             mOurConnectionServiceImpl.addRemoteConference(conference);
219         }
220 
221         @Override
222         public void removeCall(String callId) {
223             if (mConnectionById.containsKey(callId)) {
224                 findConnectionForAction(callId, "removeCall")
225                         .setDestroyed();
226             } else {
227                 findConferenceForAction(callId, "removeCall")
228                         .setDestroyed();
229             }
230         }
231 
232         @Override
233         public void onPostDialWait(String callId, String remaining) {
234             findConnectionForAction(callId, "onPostDialWait")
235                     .setPostDialWait(remaining);
236         }
237 
238         @Override
239         public void onPostDialChar(String callId, char nextChar) {
240             findConnectionForAction(callId, "onPostDialChar")
241                     .onPostDialChar(nextChar);
242         }
243 
244         @Override
245         public void queryRemoteConnectionServices(RemoteServiceCallback callback) {
246             // Not supported from remote connection service.
247         }
248 
249         @Override
250         public void setVideoProvider(String callId, IVideoProvider videoProvider) {
251             RemoteConnection.VideoProvider remoteVideoProvider = null;
252             if (videoProvider != null) {
253                 remoteVideoProvider = new RemoteConnection.VideoProvider(videoProvider);
254             }
255             findConnectionForAction(callId, "setVideoProvider")
256                     .setVideoProvider(remoteVideoProvider);
257         }
258 
259         @Override
260         public void setVideoState(String callId, int videoState) {
261             findConnectionForAction(callId, "setVideoState")
262                     .setVideoState(videoState);
263         }
264 
265         @Override
266         public void setIsVoipAudioMode(String callId, boolean isVoip) {
267             findConnectionForAction(callId, "setIsVoipAudioMode")
268                     .setIsVoipAudioMode(isVoip);
269         }
270 
271         @Override
272         public void setStatusHints(String callId, StatusHints statusHints) {
273             findConnectionForAction(callId, "setStatusHints")
274                     .setStatusHints(statusHints);
275         }
276 
277         @Override
278         public void setAddress(String callId, Uri address, int presentation) {
279             findConnectionForAction(callId, "setAddress")
280                     .setAddress(address, presentation);
281         }
282 
283         @Override
284         public void setCallerDisplayName(String callId, String callerDisplayName,
285                 int presentation) {
286             findConnectionForAction(callId, "setCallerDisplayName")
287                     .setCallerDisplayName(callerDisplayName, presentation);
288         }
289 
290         @Override
291         public IBinder asBinder() {
292             throw new UnsupportedOperationException();
293         }
294 
295         @Override
296         public final void setConferenceableConnections(
297                 String callId, List<String> conferenceableConnectionIds) {
298             List<RemoteConnection> conferenceable = new ArrayList<>();
299             for (String id : conferenceableConnectionIds) {
300                 if (mConnectionById.containsKey(id)) {
301                     conferenceable.add(mConnectionById.get(id));
302                 }
303             }
304 
305             if (hasConnection(callId)) {
306                 findConnectionForAction(callId, "setConferenceableConnections")
307                         .setConferenceableConnections(conferenceable);
308             } else {
309                 findConferenceForAction(callId, "setConferenceableConnections")
310                         .setConferenceableConnections(conferenceable);
311             }
312         }
313 
314         @Override
315         public void addExistingConnection(String callId, ParcelableConnection connection) {
316             // TODO: add contents of this method
317             RemoteConnection remoteConnction = new RemoteConnection(callId,
318                     mOutgoingConnectionServiceRpc, connection);
319 
320             mOurConnectionServiceImpl.addRemoteExistingConnection(remoteConnction);
321         }
322 
323         @Override
324         public void setExtras(String callId, Bundle extras) {
325             if (mConnectionById.containsKey(callId)) {
326                 findConnectionForAction(callId, "setExtras")
327                         .setExtras(extras);
328             } else {
329                 findConferenceForAction(callId, "setExtras")
330                         .setExtras(extras);
331             }
332         }
333     };
334 
335     private final ConnectionServiceAdapterServant mServant =
336             new ConnectionServiceAdapterServant(mServantDelegate);
337 
338     private final DeathRecipient mDeathRecipient = new DeathRecipient() {
339         @Override
340         public void binderDied() {
341             for (RemoteConnection c : mConnectionById.values()) {
342                 c.setDestroyed();
343             }
344             for (RemoteConference c : mConferenceById.values()) {
345                 c.setDestroyed();
346             }
347             mConnectionById.clear();
348             mConferenceById.clear();
349             mPendingConnections.clear();
350             mOutgoingConnectionServiceRpc.asBinder().unlinkToDeath(mDeathRecipient, 0);
351         }
352     };
353 
354     private final IConnectionService mOutgoingConnectionServiceRpc;
355     private final ConnectionService mOurConnectionServiceImpl;
356     private final Map<String, RemoteConnection> mConnectionById = new HashMap<>();
357     private final Map<String, RemoteConference> mConferenceById = new HashMap<>();
358     private final Set<RemoteConnection> mPendingConnections = new HashSet<>();
359 
RemoteConnectionService( IConnectionService outgoingConnectionServiceRpc, ConnectionService ourConnectionServiceImpl)360     RemoteConnectionService(
361             IConnectionService outgoingConnectionServiceRpc,
362             ConnectionService ourConnectionServiceImpl) throws RemoteException {
363         mOutgoingConnectionServiceRpc = outgoingConnectionServiceRpc;
364         mOutgoingConnectionServiceRpc.asBinder().linkToDeath(mDeathRecipient, 0);
365         mOurConnectionServiceImpl = ourConnectionServiceImpl;
366     }
367 
368     @Override
toString()369     public String toString() {
370         return "[RemoteCS - " + mOutgoingConnectionServiceRpc.asBinder().toString() + "]";
371     }
372 
createRemoteConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request, boolean isIncoming)373     final RemoteConnection createRemoteConnection(
374             PhoneAccountHandle connectionManagerPhoneAccount,
375             ConnectionRequest request,
376             boolean isIncoming) {
377         final String id = UUID.randomUUID().toString();
378         final ConnectionRequest newRequest = new ConnectionRequest(
379                 request.getAccountHandle(),
380                 request.getAddress(),
381                 request.getExtras(),
382                 request.getVideoState());
383         try {
384             if (mConnectionById.isEmpty()) {
385                 mOutgoingConnectionServiceRpc.addConnectionServiceAdapter(mServant.getStub());
386             }
387             RemoteConnection connection =
388                     new RemoteConnection(id, mOutgoingConnectionServiceRpc, newRequest);
389             mPendingConnections.add(connection);
390             mConnectionById.put(id, connection);
391             mOutgoingConnectionServiceRpc.createConnection(
392                     connectionManagerPhoneAccount,
393                     id,
394                     newRequest,
395                     isIncoming,
396                     false /* isUnknownCall */);
397             connection.registerCallback(new RemoteConnection.Callback() {
398                 @Override
399                 public void onDestroyed(RemoteConnection connection) {
400                     mConnectionById.remove(id);
401                     maybeDisconnectAdapter();
402                 }
403             });
404             return connection;
405         } catch (RemoteException e) {
406             return RemoteConnection.failure(
407                     new DisconnectCause(DisconnectCause.ERROR, e.toString()));
408         }
409     }
410 
hasConnection(String callId)411     private boolean hasConnection(String callId) {
412         return mConnectionById.containsKey(callId);
413     }
414 
findConnectionForAction( String callId, String action)415     private RemoteConnection findConnectionForAction(
416             String callId, String action) {
417         if (mConnectionById.containsKey(callId)) {
418             return mConnectionById.get(callId);
419         }
420         Log.w(this, "%s - Cannot find Connection %s", action, callId);
421         return NULL_CONNECTION;
422     }
423 
findConferenceForAction( String callId, String action)424     private RemoteConference findConferenceForAction(
425             String callId, String action) {
426         if (mConferenceById.containsKey(callId)) {
427             return mConferenceById.get(callId);
428         }
429         Log.w(this, "%s - Cannot find Conference %s", action, callId);
430         return NULL_CONFERENCE;
431     }
432 
maybeDisconnectAdapter()433     private void maybeDisconnectAdapter() {
434         if (mConnectionById.isEmpty() && mConferenceById.isEmpty()) {
435             try {
436                 mOutgoingConnectionServiceRpc.removeConnectionServiceAdapter(mServant.getStub());
437             } catch (RemoteException e) {
438             }
439         }
440     }
441 }
442