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