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