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