blob: b2dd08515deb8e3053e3bb75a1bf69154b0bac32 [file] [log] [blame]
Sailesh Nepalc92c4362014-07-04 18:33:21 -07001/*
2 * Copyright 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 com.android.telecomm;
18
19import android.content.ComponentName;
20import android.os.Bundle;
21import android.os.Handler;
22import android.os.IBinder;
23import android.os.Message;
24import android.os.RemoteException;
25import android.telecomm.CallAudioState;
26import android.telecomm.ConnectionService;
27import android.telecomm.CallServiceDescriptor;
28import android.telecomm.ConnectionRequest;
29import android.telecomm.GatewayInfo;
Sailesh Nepal35faf8c2014-07-08 22:02:34 -070030import android.telecomm.StatusHints;
Sailesh Nepalc92c4362014-07-04 18:33:21 -070031import android.telecomm.TelecommConstants;
32import android.telephony.DisconnectCause;
33
34import com.android.internal.os.SomeArgs;
Sailesh Nepalc92c4362014-07-04 18:33:21 -070035import com.android.internal.telecomm.IConnectionService;
36import com.android.internal.telecomm.IConnectionServiceAdapter;
37import com.android.internal.telecomm.ICallServiceProvider;
38import com.android.internal.telecomm.ICallVideoProvider;
39import com.android.internal.telecomm.RemoteServiceCallback;
40import com.android.telecomm.BaseRepository.LookupCallback;
41import com.google.common.base.Preconditions;
42import com.google.common.collect.ImmutableList;
43
44import org.apache.http.conn.ClientConnectionRequest;
45
46import java.util.ArrayList;
47import java.util.Collection;
48import java.util.HashMap;
49import java.util.HashSet;
50import java.util.List;
51import java.util.Map;
52import java.util.Set;
53
54/**
55 * Wrapper for {@link IConnectionService}s, handles binding to {@link IConnectionService} and keeps
56 * track of when the object can safely be unbound. Other classes should not use
57 * {@link IConnectionService} directly and instead should use this class to invoke methods of
58 * {@link IConnectionService}.
59 */
60final class ConnectionServiceWrapper extends ServiceBinder<IConnectionService> {
61 private static final int MSG_NOTIFY_INCOMING_CALL = 1;
62 private static final int MSG_HANDLE_SUCCESSFUL_OUTGOING_CALL = 2;
63 private static final int MSG_HANDLE_FAILED_OUTGOING_CALL = 3;
64 private static final int MSG_CANCEL_OUTGOING_CALL = 4;
65 private static final int MSG_SET_ACTIVE = 5;
66 private static final int MSG_SET_RINGING = 6;
67 private static final int MSG_SET_DIALING = 7;
68 private static final int MSG_SET_DISCONNECTED = 8;
69 private static final int MSG_SET_ON_HOLD = 9;
70 private static final int MSG_SET_REQUESTING_RINGBACK = 10;
Sailesh Nepale20bc972014-07-09 21:22:36 -070071 private static final int MSG_SET_CALL_CAPABILITIES = 11;
Sailesh Nepalc92c4362014-07-04 18:33:21 -070072 private static final int MSG_SET_IS_CONFERENCED = 12;
73 private static final int MSG_ADD_CONFERENCE_CALL = 13;
74 private static final int MSG_REMOVE_CALL = 14;
75 private static final int MSG_ON_POST_DIAL_WAIT = 15;
76 private static final int MSG_QUERY_REMOTE_CALL_SERVICES = 16;
77 private static final int MSG_SET_CALL_VIDEO_PROVIDER = 17;
78 private static final int MSG_SET_FEATURES = 18;
Sailesh Nepal7e669572014-07-08 21:29:12 -070079 private static final int MSG_SET_AUDIO_MODE_IS_VOIP = 19;
Sailesh Nepal35faf8c2014-07-08 22:02:34 -070080 private static final int MSG_SET_STATUS_HINTS = 20;
Sailesh Nepalc92c4362014-07-04 18:33:21 -070081
82 private final Handler mHandler = new Handler() {
83 @Override
84 public void handleMessage(Message msg) {
85 Call call;
86 switch (msg.what) {
87 case MSG_NOTIFY_INCOMING_CALL: {
88 ConnectionRequest request = (ConnectionRequest) msg.obj;
89 call = mCallIdMapper.getCall(request.getCallId());
90 if (call != null && mPendingIncomingCalls.remove(call) &&
91 call.isIncoming()) {
92 mIncomingCallsManager.handleSuccessfulIncomingCall(call, request);
93 } else {
94 // TODO(santoscordon): For this an the other commented logging, we need
95 // to reenable it. At the moment all ConnectionServiceAdapters receive
96 // notification of changes to all calls, even calls which it may not own
97 // (ala remote connections). We need to fix that and then uncomment the
98 // logging calls here.
99 //Log.w(this, "notifyIncomingCall, unknown incoming call: %s, id: %s",
100 // call, request.getId());
101 }
102 break;
103 }
104 case MSG_HANDLE_SUCCESSFUL_OUTGOING_CALL: {
105 ConnectionRequest request = (ConnectionRequest) msg.obj;
106 if (mPendingOutgoingCalls.containsKey(request.getCallId())) {
107 mPendingOutgoingCalls.remove(
108 request.getCallId()).onOutgoingCallSuccess();
109 } else {
110 //Log.w(this, "handleSuccessfulOutgoingCall, unknown call: %s", callId);
111 }
112 break;
113 }
114 case MSG_HANDLE_FAILED_OUTGOING_CALL: {
115 SomeArgs args = (SomeArgs) msg.obj;
116 try {
Sailesh Nepala49c6432014-07-07 22:47:11 -0700117 ConnectionRequest request = (ConnectionRequest) args.arg1;
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700118 int statusCode = args.argi1;
119 String statusMsg = (String) args.arg2;
120 // TODO(santoscordon): Do something with 'reason' or get rid of it.
121
122 if (mPendingOutgoingCalls.containsKey(request.getCallId())) {
123 mPendingOutgoingCalls.remove(request.getCallId())
124 .onOutgoingCallFailure(statusCode, statusMsg);
125 mCallIdMapper.removeCall(request.getCallId());
126 } else {
127 //Log.w(this, "handleFailedOutgoingCall, unknown call: %s", callId);
128 }
129 } finally {
130 args.recycle();
131 }
132 break;
133 }
134 case MSG_CANCEL_OUTGOING_CALL: {
135 ConnectionRequest request = (ConnectionRequest) msg.obj;
136 if (mPendingOutgoingCalls.containsKey(request.getCallId())) {
137 mPendingOutgoingCalls.remove(
138 request.getCallId()).onOutgoingCallCancel();
139 } else {
140 //Log.w(this, "cancelOutgoingCall, unknown call: %s", callId);
141 }
142 break;
143 }
144 case MSG_SET_ACTIVE:
145 call = mCallIdMapper.getCall(msg.obj);
146 if (call != null) {
147 mCallsManager.markCallAsActive(call);
148 } else {
149 //Log.w(this, "setActive, unknown call id: %s", msg.obj);
150 }
151 break;
152 case MSG_SET_RINGING:
153 call = mCallIdMapper.getCall(msg.obj);
154 if (call != null) {
155 mCallsManager.markCallAsRinging(call);
156 } else {
157 //Log.w(this, "setRinging, unknown call id: %s", msg.obj);
158 }
159 break;
160 case MSG_SET_DIALING:
161 call = mCallIdMapper.getCall(msg.obj);
162 if (call != null) {
163 mCallsManager.markCallAsDialing(call);
164 } else {
165 //Log.w(this, "setDialing, unknown call id: %s", msg.obj);
166 }
167 break;
168 case MSG_SET_DISCONNECTED: {
169 SomeArgs args = (SomeArgs) msg.obj;
170 try {
171 call = mCallIdMapper.getCall(args.arg1);
172 String disconnectMessage = (String) args.arg2;
173 int disconnectCause = args.argi1;
174 if (call != null) {
175 mCallsManager.markCallAsDisconnected(call, disconnectCause,
176 disconnectMessage);
177 } else {
178 //Log.w(this, "setDisconnected, unknown call id: %s", args.arg1);
179 }
180 } finally {
181 args.recycle();
182 }
183 break;
184 }
185 case MSG_SET_ON_HOLD:
186 call = mCallIdMapper.getCall(msg.obj);
187 if (call != null) {
188 mCallsManager.markCallAsOnHold(call);
189 } else {
190 //Log.w(this, "setOnHold, unknown call id: %s", msg.obj);
191 }
192 break;
193 case MSG_SET_REQUESTING_RINGBACK: {
194 SomeArgs args = (SomeArgs) msg.obj;
195 try {
196 call = mCallIdMapper.getCall(args.arg1);
197 boolean ringback = (boolean) args.arg2;
198 if (call != null) {
199 call.setRequestingRingback(ringback);
200 } else {
201 //Log.w(this, "setRingback, unknown call id: %s", args.arg1);
202 }
203 } finally {
204 args.recycle();
205 }
206 break;
207 }
Sailesh Nepale20bc972014-07-09 21:22:36 -0700208 case MSG_SET_CALL_CAPABILITIES: {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700209 call = mCallIdMapper.getCall(msg.obj);
210 if (call != null) {
Sailesh Nepale20bc972014-07-09 21:22:36 -0700211 call.setCallCapabilities(msg.arg1);
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700212 } else {
213 //Log.w(ConnectionServiceWrapper.this,
Sailesh Nepale20bc972014-07-09 21:22:36 -0700214 // "setCallCapabilities, unknown call id: %s", msg.obj);
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700215 }
216 break;
217 }
218 case MSG_SET_IS_CONFERENCED: {
219 SomeArgs args = (SomeArgs) msg.obj;
220 try {
221 Call childCall = mCallIdMapper.getCall(args.arg1);
222 if (childCall != null) {
223 String conferenceCallId = (String) args.arg2;
224 if (conferenceCallId == null) {
225 childCall.setParentCall(null);
226 } else {
227 Call conferenceCall = mCallIdMapper.getCall(conferenceCallId);
228 if (conferenceCall != null &&
229 !mPendingConferenceCalls.contains(conferenceCall)) {
230 childCall.setParentCall(conferenceCall);
231 } else {
232 //Log.w(this, "setIsConferenced, unknown conference id %s",
233 // conferenceCallId);
234 }
235 }
236 } else {
237 //Log.w(this, "setIsConferenced, unknown call id: %s", args.arg1);
238 }
239 } finally {
240 args.recycle();
241 }
242 break;
243 }
244 case MSG_ADD_CONFERENCE_CALL: {
245 Call conferenceCall = mCallIdMapper.getCall(msg.obj);
246 if (mPendingConferenceCalls.remove(conferenceCall)) {
247 Log.v(this, "confirming conf call %s", conferenceCall);
248 conferenceCall.confirmConference();
249 } else {
250 //Log.w(this, "addConference, unknown call id: %s", callId);
251 }
252 break;
253 }
254 case MSG_REMOVE_CALL:
255 break;
256 case MSG_ON_POST_DIAL_WAIT: {
257 SomeArgs args = (SomeArgs) msg.obj;
258 try {
259 call = mCallIdMapper.getCall(args.arg1);
260 if (call != null) {
261 String remaining = (String) args.arg2;
262 call.onPostDialWait(remaining);
263 } else {
264 //Log.w(this, "onPostDialWait, unknown call id: %s", args.arg1);
265 }
266 } finally {
267 args.recycle();
268 }
269 break;
270 }
271 case MSG_QUERY_REMOTE_CALL_SERVICES: {
272 ConnectionServiceWrapper.this.queryRemoteConnectionServices(
273 (RemoteServiceCallback) msg.obj);
274 break;
275 }
276 case MSG_SET_CALL_VIDEO_PROVIDER: {
277 SomeArgs args = (SomeArgs) msg.obj;
278 try {
279 call = mCallIdMapper.getCall(args.arg1);
280 ICallVideoProvider callVideoProvider = (ICallVideoProvider) args.arg2;
281 if (call != null) {
282 call.setCallVideoProvider(callVideoProvider);
283 }
284 } finally {
285 args.recycle();
286 }
287 break;
288 }
289 case MSG_SET_FEATURES: {
Sailesh Nepale20bc972014-07-09 21:22:36 -0700290 call = mCallIdMapper.getCall(msg.obj);
291 if (call != null) {
292 call.setFeatures(msg.arg1);
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700293 }
294 break;
295 }
Sailesh Nepal7e669572014-07-08 21:29:12 -0700296 case MSG_SET_AUDIO_MODE_IS_VOIP: {
Sailesh Nepale20bc972014-07-09 21:22:36 -0700297 call = mCallIdMapper.getCall(msg.obj);
298 if (call != null) {
299 call.setAudioModeIsVoip(msg.arg1 == 1);
Sailesh Nepal7e669572014-07-08 21:29:12 -0700300 }
301 break;
302 }
Sailesh Nepal35faf8c2014-07-08 22:02:34 -0700303 case MSG_SET_STATUS_HINTS: {
304 SomeArgs args = (SomeArgs) msg.obj;
305 try {
306 call = mCallIdMapper.getCall(args.arg1);
307 StatusHints statusHints = (StatusHints) args.arg2;
308 if (call != null) {
309 call.setStatusHints(statusHints);
310 }
311 } finally {
312 args.recycle();
313 }
314 break;
315 }
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700316 }
317 }
318 };
319
320 private final class Adapter extends IConnectionServiceAdapter.Stub {
321 /** {@inheritDoc} */
322 @Override
323 public void notifyIncomingCall(ConnectionRequest request) {
324 logIncoming("notifyIncomingCall %s", request);
325 mCallIdMapper.checkValidCallId(request.getCallId());
326 mHandler.obtainMessage(MSG_NOTIFY_INCOMING_CALL, request).sendToTarget();
327 }
328
329 /** {@inheritDoc} */
330 @Override
331 public void handleSuccessfulOutgoingCall(ConnectionRequest request) {
332 logIncoming("handleSuccessfulOutgoingCall %s", request);
333 mCallIdMapper.checkValidCallId(request.getCallId());
334 mHandler.obtainMessage(MSG_HANDLE_SUCCESSFUL_OUTGOING_CALL, request).sendToTarget();
335 }
336
337 /** {@inheritDoc} */
338 @Override
339 public void handleFailedOutgoingCall(
340 ConnectionRequest request,
341 int errorCode,
342 String errorMsg) {
343 logIncoming("handleFailedOutgoingCall %s %d %s", request, errorCode, errorMsg);
344 mCallIdMapper.checkValidCallId(request.getCallId());
345 SomeArgs args = SomeArgs.obtain();
346 args.arg1 = request;
347 args.argi1 = errorCode;
348 args.arg2 = errorMsg;
349 mHandler.obtainMessage(MSG_HANDLE_FAILED_OUTGOING_CALL, args).sendToTarget();
350 }
351
352 /** {@inheritDoc} */
353 @Override
354 public void cancelOutgoingCall(ConnectionRequest request) {
355 logIncoming("cancelOutgoingCall %s", request);
356 mCallIdMapper.checkValidCallId(request.getCallId());
357 mHandler.obtainMessage(MSG_CANCEL_OUTGOING_CALL, request).sendToTarget();
358 }
359
360 /** {@inheritDoc} */
361 @Override
362 public void setActive(String callId) {
363 logIncoming("setActive %s", callId);
364 mCallIdMapper.checkValidCallId(callId);
365 mHandler.obtainMessage(MSG_SET_ACTIVE, callId).sendToTarget();
366 }
367
368 /** {@inheritDoc} */
369 @Override
370 public void setRinging(String callId) {
371 logIncoming("setRinging %s", callId);
372 mCallIdMapper.checkValidCallId(callId);
373 mHandler.obtainMessage(MSG_SET_RINGING, callId).sendToTarget();
374 }
375
376 /** {@inheritDoc} */
377 @Override
378 public void setCallVideoProvider(String callId, ICallVideoProvider callVideoProvider) {
379 logIncoming("setCallVideoProvider %s", callId);
380 mCallIdMapper.checkValidCallId(callId);
381 SomeArgs args = SomeArgs.obtain();
382 args.arg1 = callId;
383 args.arg2 = callVideoProvider;
384 mHandler.obtainMessage(MSG_SET_CALL_VIDEO_PROVIDER, args).sendToTarget();
385 }
386
387 /** {@inheritDoc} */
388 @Override
389 public void setDialing(String callId) {
390 logIncoming("setDialing %s", callId);
391 mCallIdMapper.checkValidCallId(callId);
392 mHandler.obtainMessage(MSG_SET_DIALING, callId).sendToTarget();
393 }
394
395 /** {@inheritDoc} */
396 @Override
397 public void setDisconnected(
398 String callId, int disconnectCause, String disconnectMessage) {
399 logIncoming("setDisconnected %s %d %s", callId, disconnectCause, disconnectMessage);
400 mCallIdMapper.checkValidCallId(callId);
401 SomeArgs args = SomeArgs.obtain();
402 args.arg1 = callId;
403 args.arg2 = disconnectMessage;
404 args.argi1 = disconnectCause;
405 mHandler.obtainMessage(MSG_SET_DISCONNECTED, args).sendToTarget();
406 }
407
408 /** {@inheritDoc} */
409 @Override
410 public void setOnHold(String callId) {
411 logIncoming("setOnHold %s", callId);
412 mCallIdMapper.checkValidCallId(callId);
413 mHandler.obtainMessage(MSG_SET_ON_HOLD, callId).sendToTarget();
414 }
415
416 /** {@inheritDoc} */
417 @Override
418 public void setRequestingRingback(String callId, boolean ringback) {
419 logIncoming("setRequestingRingback %s %b", callId, ringback);
420 mCallIdMapper.checkValidCallId(callId);
421 SomeArgs args = SomeArgs.obtain();
422 args.arg1 = callId;
423 args.arg2 = ringback;
424 mHandler.obtainMessage(MSG_SET_REQUESTING_RINGBACK, args).sendToTarget();
425 }
426
427 /** ${inheritDoc} */
428 @Override
429 public void removeCall(String callId) {
430 logIncoming("removeCall %s", callId);
431 }
432
433 /** ${inheritDoc} */
434 @Override
Sailesh Nepale20bc972014-07-09 21:22:36 -0700435 public void setCallCapabilities(String callId, int callCapabilities) {
436 logIncoming("setCallCapabilities %s %d", callId, callCapabilities);
437 mHandler.obtainMessage(MSG_SET_CALL_CAPABILITIES, callCapabilities, 0, callId)
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700438 .sendToTarget();
439 }
440
441 /** ${inheritDoc} */
442 @Override
443 public void setIsConferenced(String callId, String conferenceCallId) {
444 logIncoming("setIsConferenced %s %s", callId, conferenceCallId);
445 SomeArgs args = SomeArgs.obtain();
446 args.arg1 = callId;
447 args.arg2 = conferenceCallId;
448 mHandler.obtainMessage(MSG_SET_IS_CONFERENCED, args).sendToTarget();
449 }
450
451 /** ${InheritDoc} */
452 @Override
453 public void addConferenceCall(String callId) {
454 logIncoming("addConferenceCall %s", callId);
455 mCallIdMapper.checkValidCallId(callId);
456 mHandler.obtainMessage(MSG_ADD_CONFERENCE_CALL, callId).sendToTarget();
457 }
458
459 @Override
460 public void onPostDialWait(String callId, String remaining) throws RemoteException {
461 logIncoming("onPostDialWait %s %s", callId, remaining);
462 mCallIdMapper.checkValidCallId(callId);
463 SomeArgs args = SomeArgs.obtain();
464 args.arg1 = callId;
465 args.arg2 = remaining;
466 mHandler.obtainMessage(MSG_ON_POST_DIAL_WAIT, args).sendToTarget();
467 }
468
469 /** ${inheritDoc} */
470 @Override
471 public void queryRemoteConnectionServices(RemoteServiceCallback callback) {
472 logIncoming("queryRemoteCSs");
473 mHandler.obtainMessage(MSG_QUERY_REMOTE_CALL_SERVICES, callback).sendToTarget();
474 }
475
476 @Override
477 public void setFeatures(String callId, int features) {
478 logIncoming("setFeatures %s %d", callId, features);
479 mCallIdMapper.checkValidCallId(callId);
Sailesh Nepale20bc972014-07-09 21:22:36 -0700480 mHandler.obtainMessage(MSG_SET_FEATURES, features, 0, callId).sendToTarget();
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700481 }
Sailesh Nepal7e669572014-07-08 21:29:12 -0700482
483 @Override
484 public void setAudioModeIsVoip(String callId, boolean isVoip) {
485 logIncoming("setAudioModeIsVoip %s %d", callId, isVoip);
486 mCallIdMapper.checkValidCallId(callId);
Sailesh Nepale20bc972014-07-09 21:22:36 -0700487 mHandler.obtainMessage(MSG_SET_AUDIO_MODE_IS_VOIP, isVoip ? 1 : 0, 0,
488 callId).sendToTarget();
Sailesh Nepal7e669572014-07-08 21:29:12 -0700489 }
Sailesh Nepal35faf8c2014-07-08 22:02:34 -0700490
491 @Override
492 public void setStatusHints(String callId, StatusHints statusHints) {
493 logIncoming("setStatusHints %s %s", callId, statusHints);
494 mCallIdMapper.checkValidCallId(callId);
495 SomeArgs args = SomeArgs.obtain();
496 args.arg1 = callId;
497 args.arg2 = statusHints;
498 mHandler.obtainMessage(MSG_SET_STATUS_HINTS, args).sendToTarget();
499 }
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700500 }
501
502 private final Adapter mAdapter = new Adapter();
503 private final CallsManager mCallsManager = CallsManager.getInstance();
504 private final Set<Call> mPendingIncomingCalls = new HashSet<>();
505 private final Set<Call> mPendingConferenceCalls = new HashSet<>();
506 private final CallServiceDescriptor mDescriptor;
507 private final CallIdMapper mCallIdMapper = new CallIdMapper("ConnectionService");
508 private final IncomingCallsManager mIncomingCallsManager;
509 private final Map<String, OutgoingCallResponse> mPendingOutgoingCalls = new HashMap<>();
510
511 private Binder mBinder = new Binder();
512 private IConnectionService mServiceInterface;
513 private final CallServiceRepository mCallServiceRepository;
514
515 /**
516 * Creates a call-service for the specified descriptor.
517 *
518 * @param descriptor The call-service descriptor from
519 * {@link ICallServiceProvider#lookupCallServices}.
520 * @param incomingCallsManager Manages the incoming call initialization flow.
521 * @param callServiceRepository Connection service repository.
522 */
523 ConnectionServiceWrapper(
524 CallServiceDescriptor descriptor,
525 IncomingCallsManager incomingCallsManager,
526 CallServiceRepository callServiceRepository) {
527 super(TelecommConstants.ACTION_CONNECTION_SERVICE, descriptor.getServiceComponent());
528 mDescriptor = descriptor;
529 mIncomingCallsManager = incomingCallsManager;
530 mCallServiceRepository = callServiceRepository;
531 }
532
533 CallServiceDescriptor getDescriptor() {
534 return mDescriptor;
535 }
536
537 /** See {@link IConnectionService#addConnectionServiceAdapter}. */
538 private void addConnectionServiceAdapter(IConnectionServiceAdapter adapter) {
539 if (isServiceValid("addConnectionServiceAdapter")) {
540 try {
Sailesh Nepal3fe8b722014-07-08 10:07:26 -0700541 logOutgoing("addConnectionServiceAdapter %s", adapter);
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700542 mServiceInterface.addConnectionServiceAdapter(adapter);
543 } catch (RemoteException e) {
544 }
545 }
546 }
547
548 /**
549 * Attempts to place the specified call, see {@link IConnectionService#call}. Returns the result
550 * asynchronously through the specified callback.
551 */
552 void call(final Call call, final OutgoingCallResponse callResponse) {
553 Log.d(this, "call(%s) via %s.", call, getComponentName());
554 BindCallback callback = new BindCallback() {
555 @Override
556 public void onSuccess() {
557 String callId = mCallIdMapper.getCallId(call);
558 mPendingOutgoingCalls.put(callId, callResponse);
559
560 GatewayInfo gatewayInfo = call.getGatewayInfo();
561 Bundle extras = call.getExtras();
562 if (gatewayInfo != null && gatewayInfo.getGatewayProviderPackageName() != null &&
563 gatewayInfo.getOriginalHandle() != null) {
564 extras = (Bundle) extras.clone();
565 extras.putString(
566 NewOutgoingCallIntentBroadcaster.EXTRA_GATEWAY_PROVIDER_PACKAGE,
567 gatewayInfo.getGatewayProviderPackageName());
568 extras.putParcelable(
569 NewOutgoingCallIntentBroadcaster.EXTRA_GATEWAY_ORIGINAL_URI,
570 gatewayInfo.getOriginalHandle());
571 }
Tyler Gunnc4abd912014-07-08 14:22:10 -0700572 ConnectionRequest request = new ConnectionRequest(callId, call.getHandle(), extras,
573 call.getVideoState());
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700574
575 try {
576 mServiceInterface.call(request);
577 } catch (RemoteException e) {
578 Log.e(this, e, "Failure to call -- %s", getDescriptor());
579 mPendingOutgoingCalls.remove(callId).onOutgoingCallFailure(
580 DisconnectCause.ERROR_UNSPECIFIED, e.toString());
581 }
582 }
583
584 @Override
585 public void onFailure() {
586 Log.e(this, new Exception(), "Failure to call %s", getDescriptor());
587 callResponse.onOutgoingCallFailure(DisconnectCause.ERROR_UNSPECIFIED, null);
588 }
589 };
590
591 mBinder.bind(callback);
592 }
593
594 /** @see ConnectionService#abort(String) */
595 void abort(Call call) {
596 // Clear out any pending outgoing call data
597 String callId = mCallIdMapper.getCallId(call);
598
599 // If still bound, tell the connection service to abort.
600 if (isServiceValid("abort")) {
601 try {
602 logOutgoing("abort %s", callId);
603 mServiceInterface.abort(callId);
604 } catch (RemoteException e) {
605 }
606 }
607
608 removeCall(call);
609 }
610
611 /** @see ConnectionService#hold(String) */
612 void hold(Call call) {
613 if (isServiceValid("hold")) {
614 try {
615 logOutgoing("hold %s", mCallIdMapper.getCallId(call));
616 mServiceInterface.hold(mCallIdMapper.getCallId(call));
617 } catch (RemoteException e) {
618 }
619 }
620 }
621
622 /** @see ConnectionService#unhold(String) */
623 void unhold(Call call) {
624 if (isServiceValid("unhold")) {
625 try {
626 logOutgoing("unhold %s", mCallIdMapper.getCallId(call));
627 mServiceInterface.unhold(mCallIdMapper.getCallId(call));
628 } catch (RemoteException e) {
629 }
630 }
631 }
632
633 /** @see ConnectionService#onAudioStateChanged(String,CallAudioState) */
634 void onAudioStateChanged(Call activeCall, CallAudioState audioState) {
635 if (isServiceValid("onAudioStateChanged")) {
636 try {
637 logOutgoing("onAudioStateChanged %s %s",
638 mCallIdMapper.getCallId(activeCall), audioState);
639 mServiceInterface.onAudioStateChanged(mCallIdMapper.getCallId(activeCall),
640 audioState);
641 } catch (RemoteException e) {
642 }
643 }
644 }
645
646 /**
647 * Starts retrieval of details for an incoming call. Details are returned through the
648 * call-service adapter using the specified call ID. Upon failure, the specified error callback
649 * is invoked. Can be invoked even when the connection service is unbound. See
650 * {@link IConnectionService#createIncomingCall}.
651 *
652 * @param call The call used for the incoming call.
653 * @param extras The {@link ConnectionService}-provided extras which need to be sent back.
654 * @param errorCallback The callback to invoke upon failure.
655 */
656 void createIncomingCall(final Call call, final Bundle extras, final Runnable errorCallback) {
657 Log.d(this, "createIncomingCall(%s) via %s.", call, getComponentName());
658 BindCallback callback = new BindCallback() {
659 @Override
660 public void onSuccess() {
661 if (isServiceValid("createIncomingCall")) {
662 mPendingIncomingCalls.add(call);
663 String callId = mCallIdMapper.getCallId(call);
664 logOutgoing("createIncomingCall %s %s", callId, extras);
665 ConnectionRequest request = new ConnectionRequest(
Tyler Gunnc4abd912014-07-08 14:22:10 -0700666 callId, call.getHandle(), extras, call.getVideoState());
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700667 try {
668 mServiceInterface.createIncomingCall(request);
669 } catch (RemoteException e) {
670 }
671 }
672 }
673
674 @Override
675 public void onFailure() {
676 errorCallback.run();
677 }
678 };
679
680 mBinder.bind(callback);
681 }
682
683 /** @see ConnectionService#disconnect(String) */
684 void disconnect(Call call) {
685 if (isServiceValid("disconnect")) {
686 try {
687 logOutgoing("disconnect %s", mCallIdMapper.getCallId(call));
688 mServiceInterface.disconnect(mCallIdMapper.getCallId(call));
689 } catch (RemoteException e) {
690 }
691 }
692 }
693
694 /** @see ConnectionService#answer(String) */
695 void answer(Call call) {
696 if (isServiceValid("answer")) {
697 try {
698 logOutgoing("answer %s", mCallIdMapper.getCallId(call));
699 mServiceInterface.answer(mCallIdMapper.getCallId(call));
700 } catch (RemoteException e) {
701 }
702 }
703 }
704
705 /** @see ConnectionService#reject(String) */
706 void reject(Call call) {
707 if (isServiceValid("reject")) {
708 try {
709 logOutgoing("reject %s", mCallIdMapper.getCallId(call));
710 mServiceInterface.reject(mCallIdMapper.getCallId(call));
711 } catch (RemoteException e) {
712 }
713 }
714 }
715
716 /** @see ConnectionService#playDtmfTone(String,char) */
717 void playDtmfTone(Call call, char digit) {
718 if (isServiceValid("playDtmfTone")) {
719 try {
720 logOutgoing("playDtmfTone %s %c", mCallIdMapper.getCallId(call), digit);
721 mServiceInterface.playDtmfTone(mCallIdMapper.getCallId(call), digit);
722 } catch (RemoteException e) {
723 }
724 }
725 }
726
727 /** @see ConnectionService#stopDtmfTone(String) */
728 void stopDtmfTone(Call call) {
729 if (isServiceValid("stopDtmfTone")) {
730 try {
731 logOutgoing("stopDtmfTone %s", mCallIdMapper.getCallId(call));
732 mServiceInterface.stopDtmfTone(mCallIdMapper.getCallId(call));
733 } catch (RemoteException e) {
734 }
735 }
736 }
737
738 void addCall(Call call) {
739 if (mCallIdMapper.getCallId(call) == null) {
740 mCallIdMapper.addCall(call);
741 }
742 }
743
744 /**
745 * Associates newCall with this connection service by replacing callToReplace.
746 */
747 void replaceCall(Call newCall, Call callToReplace) {
748 Preconditions.checkState(callToReplace.getConnectionService() == this);
749 mCallIdMapper.replaceCall(newCall, callToReplace);
750 }
751
752 void removeCall(Call call) {
753 mPendingIncomingCalls.remove(call);
754
755 OutgoingCallResponse outgoingResultCallback =
756 mPendingOutgoingCalls.remove(mCallIdMapper.getCallId(call));
757 if (outgoingResultCallback != null) {
758 outgoingResultCallback.onOutgoingCallFailure(DisconnectCause.ERROR_UNSPECIFIED, null);
759 }
760
761 mCallIdMapper.removeCall(call);
762 }
763
764 void onPostDialContinue(Call call, boolean proceed) {
765 if (isServiceValid("onPostDialContinue")) {
766 try {
767 logOutgoing("onPostDialContinue %s %b", mCallIdMapper.getCallId(call), proceed);
768 mServiceInterface.onPostDialContinue(mCallIdMapper.getCallId(call), proceed);
769 } catch (RemoteException ignored) {
770 }
771 }
772 }
773
774 void onPhoneAccountClicked(Call call) {
775 if (isServiceValid("onPhoneAccountClicked")) {
776 try {
777 logOutgoing("onPhoneAccountClicked %s", mCallIdMapper.getCallId(call));
778 mServiceInterface.onPhoneAccountClicked(mCallIdMapper.getCallId(call));
779 } catch (RemoteException ignored) {
780 }
781 }
782 }
783
784 void conference(final Call conferenceCall, Call call) {
785 if (isServiceValid("conference")) {
786 try {
787 conferenceCall.setConnectionService(this);
788 mPendingConferenceCalls.add(conferenceCall);
789 mHandler.postDelayed(new Runnable() {
790 @Override public void run() {
791 if (mPendingConferenceCalls.remove(conferenceCall)) {
792 conferenceCall.expireConference();
793 Log.i(this, "Conference call expired: %s", conferenceCall);
794 }
795 }
796 }, Timeouts.getConferenceCallExpireMillis());
797
798 logOutgoing("conference %s %s",
799 mCallIdMapper.getCallId(conferenceCall),
800 mCallIdMapper.getCallId(call));
801 mServiceInterface.conference(
802 mCallIdMapper.getCallId(conferenceCall),
803 mCallIdMapper.getCallId(call));
804 } catch (RemoteException ignored) {
805 }
806 }
807 }
808
809 void splitFromConference(Call call) {
810 if (isServiceValid("splitFromConference")) {
811 try {
812 logOutgoing("splitFromConference %s", mCallIdMapper.getCallId(call));
813 mServiceInterface.splitFromConference(mCallIdMapper.getCallId(call));
814 } catch (RemoteException ignored) {
815 }
816 }
817 }
818
819 /** {@inheritDoc} */
820 @Override
821 protected void setServiceInterface(IBinder binder) {
822 if (binder == null) {
823 // We have lost our service connection. Notify the world that this service is done.
824 // We must notify the adapter before CallsManager. The adapter will force any pending
825 // outgoing calls to try the next service. This needs to happen before CallsManager
826 // tries to clean up any calls still associated with this service.
827 handleConnectionServiceDeath();
828 CallsManager.getInstance().handleConnectionServiceDeath(this);
829 mServiceInterface = null;
830 } else {
831 mServiceInterface = IConnectionService.Stub.asInterface(binder);
832 addConnectionServiceAdapter(mAdapter);
833 }
834 }
835
836 /**
837 * Called when the associated connection service dies.
838 */
839 private void handleConnectionServiceDeath() {
840 if (!mPendingOutgoingCalls.isEmpty()) {
841 for (OutgoingCallResponse callback : mPendingOutgoingCalls.values()) {
842 callback.onOutgoingCallFailure(DisconnectCause.ERROR_UNSPECIFIED, null);
843 }
844 mPendingOutgoingCalls.clear();
845 }
846
847 if (!mPendingIncomingCalls.isEmpty()) {
848 // Iterate through a copy because the code inside the loop will modify the original
849 // list.
850 for (Call call : ImmutableList.copyOf(mPendingIncomingCalls)) {
851 Preconditions.checkState(call.isIncoming());
852 mIncomingCallsManager.handleFailedIncomingCall(call);
853 }
854
855 if (!mPendingIncomingCalls.isEmpty()) {
856 Log.wtf(this, "Pending calls did not get cleared.");
857 mPendingIncomingCalls.clear();
858 }
859 }
860
861 mCallIdMapper.clear();
862 }
863
864 private void logIncoming(String msg, Object... params) {
865 Log.d(this, "ConnectionService -> Telecomm: " + msg, params);
866 }
867
868 private void logOutgoing(String msg, Object... params) {
869 Log.d(this, "Telecomm -> ConnectionService: " + msg, params);
870 }
871
872 private void queryRemoteConnectionServices(final RemoteServiceCallback callback) {
873 final List<IBinder> connectionServices = new ArrayList<>();
874 final List<ComponentName> components = new ArrayList<>();
875
876 mCallServiceRepository.lookupServices(new LookupCallback<ConnectionServiceWrapper>() {
877 private int mRemainingResponses;
878
879 /** ${inheritDoc} */
880 @Override
881 public void onComplete(Collection<ConnectionServiceWrapper> services) {
882 mRemainingResponses = services.size() - 1;
883 for (ConnectionServiceWrapper cs : services) {
884 if (cs != ConnectionServiceWrapper.this) {
885 final ConnectionServiceWrapper currentConnectionService = cs;
886 cs.mBinder.bind(new BindCallback() {
887 @Override
888 public void onSuccess() {
889 Log.d(this, "Adding ***** %s",
890 currentConnectionService.getDescriptor());
891 connectionServices.add(
892 currentConnectionService.mServiceInterface.asBinder());
893 components.add(currentConnectionService.getComponentName());
894 maybeComplete();
895 }
896
897 @Override
898 public void onFailure() {
899 // add null so that we always add up to totalExpected even if
900 // some of the connection services fail to bind.
901 maybeComplete();
902 }
903
904 private void maybeComplete() {
905 if (--mRemainingResponses == 0) {
906 try {
907 callback.onResult(components, connectionServices);
908 } catch (RemoteException ignored) {
909 }
910 }
911 }
912 });
913 }
914 }
915 }
916 });
917 }
918}