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