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