blob: ad6d490da438eda235e36f54aba0723e2a53b056 [file] [log] [blame]
Santos Cordon68d1a6b2014-09-19 12:25:58 -07001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.telecom;
18
19import android.app.Service;
20import android.bluetooth.BluetoothAdapter;
21import android.bluetooth.BluetoothHeadset;
22import android.bluetooth.BluetoothProfile;
23import android.bluetooth.IBluetoothHeadsetPhone;
24import android.content.BroadcastReceiver;
25import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.net.Uri;
29import android.os.Handler;
30import android.os.IBinder;
31import android.os.Message;
32import android.os.RemoteException;
Santos Cordon33fff492014-09-25 14:57:01 -070033import android.telecom.CallState;
Santos Cordon68d1a6b2014-09-19 12:25:58 -070034import android.telecom.PhoneAccount;
Santos Cordon88a4a602014-09-29 19:32:21 -070035import android.telecom.PhoneCapabilities;
Santos Cordon68d1a6b2014-09-19 12:25:58 -070036import android.telephony.PhoneNumberUtils;
37import android.telephony.TelephonyManager;
38import android.text.TextUtils;
39
40import com.android.server.telecom.CallsManager.CallsManagerListener;
41
Santos Cordon33fff492014-09-25 14:57:01 -070042import java.util.Collection;
43import java.util.HashMap;
Santos Cordon68d1a6b2014-09-19 12:25:58 -070044import java.util.List;
Santos Cordon33fff492014-09-25 14:57:01 -070045import java.util.Map;
Santos Cordon68d1a6b2014-09-19 12:25:58 -070046
47/**
48 * Bluetooth headset manager for Telecom. This class shares the call state with the bluetooth device
49 * and accepts call-related commands to perform on behalf of the BT device.
50 */
51public final class BluetoothPhoneService extends Service {
52 /**
53 * Request object for performing synchronous requests to the main thread.
54 */
55 private static class MainThreadRequest {
56 Object result;
57 int param;
58
59 MainThreadRequest(int param) {
60 this.param = param;
61 }
62
63 void setResult(Object value) {
64 result = value;
65 synchronized (this) {
66 notifyAll();
67 }
68 }
69 }
70
71 private static final String TAG = "BluetoothPhoneService";
72
73 private static final int MSG_ANSWER_CALL = 1;
74 private static final int MSG_HANGUP_CALL = 2;
75 private static final int MSG_SEND_DTMF = 3;
76 private static final int MSG_PROCESS_CHLD = 4;
77 private static final int MSG_GET_NETWORK_OPERATOR = 5;
78 private static final int MSG_LIST_CURRENT_CALLS = 6;
79 private static final int MSG_QUERY_PHONE_STATE = 7;
80 private static final int MSG_GET_SUBSCRIBER_NUMBER = 8;
81
82 // match up with bthf_call_state_t of bt_hf.h
83 private static final int CALL_STATE_ACTIVE = 0;
84 private static final int CALL_STATE_HELD = 1;
85 private static final int CALL_STATE_DIALING = 2;
86 private static final int CALL_STATE_ALERTING = 3;
87 private static final int CALL_STATE_INCOMING = 4;
88 private static final int CALL_STATE_WAITING = 5;
89 private static final int CALL_STATE_IDLE = 6;
90
91 // match up with bthf_call_state_t of bt_hf.h
92 // Terminate all held or set UDUB("busy") to a waiting call
93 private static final int CHLD_TYPE_RELEASEHELD = 0;
94 // Terminate all active calls and accepts a waiting/held call
95 private static final int CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD = 1;
96 // Hold all active calls and accepts a waiting/held call
97 private static final int CHLD_TYPE_HOLDACTIVE_ACCEPTHELD = 2;
98 // Add all held calls to a conference
99 private static final int CHLD_TYPE_ADDHELDTOCONF = 3;
100
Santos Cordona0b46122014-09-29 14:20:21 -0700101 private int mNumActiveCalls = 0;
102 private int mNumHeldCalls = 0;
103 private int mBluetoothCallState = CALL_STATE_IDLE;
104 private String mRingingAddress = null;
105 private int mRingingAddressType = 0;
106
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700107 /**
108 * Binder implementation of IBluetoothHeadsetPhone. Implements the command interface that the
109 * bluetooth headset code uses to control call.
110 */
111 private final IBluetoothHeadsetPhone.Stub mBinder = new IBluetoothHeadsetPhone.Stub() {
112 @Override
113 public boolean answerCall() throws RemoteException {
114 enforceModifyPermission();
115 Log.i(TAG, "BT - answering call");
116 return sendSynchronousRequest(MSG_ANSWER_CALL);
117 }
118
119 @Override
120 public boolean hangupCall() throws RemoteException {
121 enforceModifyPermission();
122 Log.i(TAG, "BT - hanging up call");
123 return sendSynchronousRequest(MSG_HANGUP_CALL);
124 }
125
126 @Override
127 public boolean sendDtmf(int dtmf) throws RemoteException {
128 enforceModifyPermission();
129 Log.i(TAG, "BT - sendDtmf %c", Log.DEBUG ? dtmf : '.');
130 return sendSynchronousRequest(MSG_SEND_DTMF, dtmf);
131 }
132
133 @Override
134 public String getNetworkOperator() throws RemoteException {
135 Log.i(TAG, "getNetworkOperator");
136 enforceModifyPermission();
137 return sendSynchronousRequest(MSG_GET_NETWORK_OPERATOR);
138 }
139
140 @Override
141 public String getSubscriberNumber() throws RemoteException {
142 Log.i(TAG, "getSubscriberNumber");
143 enforceModifyPermission();
144 return sendSynchronousRequest(MSG_GET_SUBSCRIBER_NUMBER);
145 }
146
147 @Override
148 public boolean listCurrentCalls() throws RemoteException {
Santos Cordon88a4a602014-09-29 19:32:21 -0700149 // only log if it is after we recently updated the headset state or else it can clog
150 // the android log since this can be queried every second.
151 boolean logQuery = mHeadsetUpdatedRecently;
152 mHeadsetUpdatedRecently = false;
153
154 if (logQuery) {
155 Log.i(TAG, "listcurrentCalls");
156 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700157 enforceModifyPermission();
Santos Cordon88a4a602014-09-29 19:32:21 -0700158 return sendSynchronousRequest(MSG_LIST_CURRENT_CALLS, logQuery ? 1 : 0);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700159 }
160
161 @Override
162 public boolean queryPhoneState() throws RemoteException {
163 Log.i(TAG, "queryPhoneState");
164 enforceModifyPermission();
165 return sendSynchronousRequest(MSG_QUERY_PHONE_STATE);
166 }
167
168 @Override
169 public boolean processChld(int chld) throws RemoteException {
170 Log.i(TAG, "processChld %d", chld);
171 enforceModifyPermission();
172 return sendSynchronousRequest(MSG_PROCESS_CHLD, chld);
173 }
174
175 @Override
176 public void updateBtHandsfreeAfterRadioTechnologyChange() throws RemoteException {
177 Log.d(TAG, "RAT change");
178 // deprecated
179 }
180
181 @Override
182 public void cdmaSetSecondCallState(boolean state) throws RemoteException {
183 Log.d(TAG, "cdma 1");
184 // deprecated
185 }
186
187 @Override
188 public void cdmaSwapSecondCallState() throws RemoteException {
189 Log.d(TAG, "cdma 2");
190 // deprecated
191 }
192 };
193
194 /**
195 * Main-thread handler for BT commands. Since telecom logic runs on a single thread, commands
196 * that are sent to it from the headset need to be moved over to the main thread before
197 * executing. This handler exists for that reason.
198 */
199 private final Handler mHandler = new Handler() {
200 @Override
201 public void handleMessage(Message msg) {
202 MainThreadRequest request = msg.obj instanceof MainThreadRequest ?
203 (MainThreadRequest) msg.obj : null;
204 CallsManager callsManager = getCallsManager();
205 Call call = null;
206
207 Log.d(TAG, "handleMessage(%d) w/ param %s",
208 msg.what, request == null ? null : request.param);
209
210 switch (msg.what) {
211 case MSG_ANSWER_CALL:
212 try {
213 call = callsManager.getRingingCall();
214 if (call != null) {
215 getCallsManager().answerCall(call, 0);
216 }
217 } finally {
218 request.setResult(call != null);
219 }
220 break;
221
222 case MSG_HANGUP_CALL:
223 try {
224 call = callsManager.getForegroundCall();
225 if (call != null) {
226 callsManager.disconnectCall(call);
227 }
228 } finally {
229 request.setResult(call != null);
230 }
231 break;
232
233 case MSG_SEND_DTMF:
234 try {
235 call = callsManager.getForegroundCall();
236 if (call != null) {
237 // TODO: Consider making this a queue instead of starting/stopping
238 // in quick succession.
239 callsManager.playDtmfTone(call, (char) request.param);
240 callsManager.stopDtmfTone(call);
241 }
242 } finally {
243 request.setResult(call != null);
244 }
245 break;
246
247 case MSG_PROCESS_CHLD:
248 Boolean result = false;
249 try {
250 result = processChld(request.param);
251 } finally {
252 request.setResult(result);
253 }
254 break;
255
256 case MSG_GET_SUBSCRIBER_NUMBER:
257 String address = null;
258 try {
259 PhoneAccount account = getBestPhoneAccount();
260 if (account != null) {
261 Uri addressUri = account.getAddress();
262 if (addressUri != null) {
263 address = addressUri.getSchemeSpecificPart();
264 }
265 }
266
267 if (TextUtils.isEmpty(address)) {
268 address = TelephonyManager.from(BluetoothPhoneService.this)
269 .getLine1Number();
270 }
271 } finally {
272 request.setResult(address);
273 }
274 break;
275
276 case MSG_GET_NETWORK_OPERATOR:
277 String label = null;
278 try {
279 PhoneAccount account = getBestPhoneAccount();
280 if (account != null) {
281 label = account.getLabel().toString();
282 } else {
283 // Finally, just get the network name from telephony.
284 label = TelephonyManager.from(BluetoothPhoneService.this)
285 .getNetworkOperatorName();
286 }
287 } finally {
288 request.setResult(label);
289 }
290 break;
291
292 case MSG_LIST_CURRENT_CALLS:
Santos Cordon33fff492014-09-25 14:57:01 -0700293 try {
Santos Cordon88a4a602014-09-29 19:32:21 -0700294 sendListOfCalls(request.param == 1);
Santos Cordon33fff492014-09-25 14:57:01 -0700295 } finally {
296 request.setResult(true);
297 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700298 break;
299
300 case MSG_QUERY_PHONE_STATE:
301 try {
302 updateHeadsetWithCallState();
303 } finally {
304 if (request != null) {
305 request.setResult(true);
306 }
307 }
308 break;
309 }
310 }
311 };
312
313 /**
314 * Listens to call changes from the CallsManager and calls into methods to update the bluetooth
315 * headset with the new states.
316 */
317 private CallsManagerListener mCallsManagerListener = new CallsManagerListenerBase() {
318 @Override
319 public void onCallAdded(Call call) {
320 updateHeadsetWithCallState();
321 }
322
323 @Override
324 public void onCallRemoved(Call call) {
Santos Cordon33fff492014-09-25 14:57:01 -0700325 mClccIndexMap.remove(call);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700326 updateHeadsetWithCallState();
327 }
328
329 @Override
330 public void onCallStateChanged(Call call, int oldState, int newState) {
331 updateHeadsetWithCallState();
332 }
333
334 @Override
335 public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
336 updateHeadsetWithCallState();
337 }
338
339 @Override
340 public void onIsConferencedChanged(Call call) {
341 updateHeadsetWithCallState();
342 }
343 };
344
345 /**
346 * Listens to connections and disconnections of bluetooth headsets. We need to save the current
347 * bluetooth headset so that we know where to send call updates.
348 */
349 private BluetoothProfile.ServiceListener mProfileListener =
350 new BluetoothProfile.ServiceListener() {
351 @Override
352 public void onServiceConnected(int profile, BluetoothProfile proxy) {
353 mBluetoothHeadset = (BluetoothHeadset) proxy;
354 }
355
356 @Override
357 public void onServiceDisconnected(int profile) {
358 mBluetoothHeadset = null;
359 }
360 };
361
362 /**
363 * Receives events for global state changes of the bluetooth adapter.
364 */
365 private final BroadcastReceiver mBluetoothAdapterReceiver = new BroadcastReceiver() {
366 @Override
367 public void onReceive(Context context, Intent intent) {
368 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
369 Log.d(TAG, "Bluetooth Adapter state: %d", state);
370 if (state == BluetoothAdapter.STATE_ON) {
371 mHandler.sendEmptyMessage(MSG_QUERY_PHONE_STATE);
372 }
373 }
374 };
375
376 private BluetoothAdapter mBluetoothAdapter;
377 private BluetoothHeadset mBluetoothHeadset;
378
Santos Cordon33fff492014-09-25 14:57:01 -0700379 // A map from Calls to indexes used to identify calls for CLCC (C* List Current Calls).
380 private Map<Call, Integer> mClccIndexMap = new HashMap<>();
381
Santos Cordon88a4a602014-09-29 19:32:21 -0700382 private boolean mHeadsetUpdatedRecently = false;
383
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700384 public BluetoothPhoneService() {
385 Log.v(TAG, "Constructor");
386 }
387
388 public static final void start(Context context) {
389 if (BluetoothAdapter.getDefaultAdapter() != null) {
390 context.startService(new Intent(context, BluetoothPhoneService.class));
391 }
392 }
393
394 @Override
395 public IBinder onBind(Intent intent) {
396 Log.d(TAG, "Binding service");
397 return mBinder;
398 }
399
400 @Override
401 public void onCreate() {
402 Log.d(TAG, "onCreate");
403
404 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
405 if (mBluetoothAdapter == null) {
406 Log.d(TAG, "BluetoothPhoneService shutting down, no BT Adapter found.");
407 return;
408 }
409 mBluetoothAdapter.getProfileProxy(this, mProfileListener, BluetoothProfile.HEADSET);
410
411 IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
412 registerReceiver(mBluetoothAdapterReceiver, intentFilter);
413
414 CallsManager.getInstance().addListener(mCallsManagerListener);
415 updateHeadsetWithCallState();
416 }
417
418 @Override
419 public void onDestroy() {
420 Log.d(TAG, "onDestroy");
421 CallsManager.getInstance().removeListener(mCallsManagerListener);
422 super.onDestroy();
423 }
424
425 private boolean processChld(int chld) {
426 CallsManager callsManager = CallsManager.getInstance();
427 Call activeCall = callsManager.getActiveCall();
428 Call ringingCall = callsManager.getRingingCall();
429 Call heldCall = callsManager.getHeldCall();
430
Santos Cordon88a4a602014-09-29 19:32:21 -0700431 Log.v(TAG, "Active: %s\nRinging: %s\nHeld: %s", activeCall, ringingCall, heldCall);
432
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700433 if (chld == CHLD_TYPE_RELEASEHELD) {
434 if (ringingCall != null) {
435 callsManager.rejectCall(ringingCall, false, null);
436 return true;
437 } else if (heldCall != null) {
438 callsManager.disconnectCall(heldCall);
439 return true;
440 }
441 } else if (chld == CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD) {
442 if (activeCall != null) {
443 callsManager.disconnectCall(activeCall);
444 if (ringingCall != null) {
445 callsManager.answerCall(ringingCall, 0);
446 } else if (heldCall != null) {
447 callsManager.unholdCall(heldCall);
448 }
449 return true;
450 }
451 } else if (chld == CHLD_TYPE_HOLDACTIVE_ACCEPTHELD) {
Santos Cordon88a4a602014-09-29 19:32:21 -0700452 if (activeCall != null && activeCall.can(PhoneCapabilities.SWAP_CONFERENCE)) {
453 activeCall.swapConference();
454 return true;
455 } else if (ringingCall != null) {
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700456 callsManager.answerCall(ringingCall, 0);
457 return true;
458 } else if (heldCall != null) {
459 // CallsManager will hold any active calls when unhold() is called on a
460 // currently-held call.
461 callsManager.unholdCall(heldCall);
462 return true;
Santos Cordon88a4a602014-09-29 19:32:21 -0700463 } else if (activeCall != null && activeCall.can(PhoneCapabilities.HOLD)) {
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700464 callsManager.holdCall(activeCall);
465 return true;
466 }
467 } else if (chld == CHLD_TYPE_ADDHELDTOCONF) {
468 if (activeCall != null) {
Santos Cordon88a4a602014-09-29 19:32:21 -0700469 if (activeCall != null && activeCall.can(PhoneCapabilities.MERGE_CONFERENCE)) {
470 activeCall.mergeConference();
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700471 return true;
Santos Cordon88a4a602014-09-29 19:32:21 -0700472 } else {
473 List<Call> conferenceable = activeCall.getConferenceableCalls();
474 if (!conferenceable.isEmpty()) {
475 callsManager.conference(activeCall, conferenceable.get(0));
476 return true;
477 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700478 }
479 }
480 }
481 return false;
482 }
483
484 private void enforceModifyPermission() {
485 enforceCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE, null);
486 }
487
488 private <T> T sendSynchronousRequest(int message) {
489 return sendSynchronousRequest(message, 0);
490 }
491
492 private <T> T sendSynchronousRequest(int message, int param) {
493 MainThreadRequest request = new MainThreadRequest(param);
494 mHandler.obtainMessage(message, request).sendToTarget();
495 synchronized (request) {
496 while (request.result == null) {
497 try {
498 request.wait();
499 } catch (InterruptedException e) {
500 // Do nothing, go back and wait until the request is complete.
501 }
502 }
503 }
504 if (request.result != null) {
505 @SuppressWarnings("unchecked")
506 T retval = (T) request.result;
507 return retval;
508 }
509 return null;
510 }
511
Santos Cordon88a4a602014-09-29 19:32:21 -0700512 private void sendListOfCalls(boolean shouldLog) {
Santos Cordon33fff492014-09-25 14:57:01 -0700513 Collection<Call> mCalls = getCallsManager().getCalls();
514 for (Call call : mCalls) {
515 // We don't send the parent conference call to the bluetooth device.
516 if (!call.isConference()) {
Santos Cordon88a4a602014-09-29 19:32:21 -0700517 sendClccForCall(call, shouldLog);
Santos Cordon33fff492014-09-25 14:57:01 -0700518 }
519 }
520 sendClccEndMarker();
521 }
522
523 /**
524 * Sends a single clcc (C* List Current Calls) event for the specified call.
525 */
Santos Cordon88a4a602014-09-29 19:32:21 -0700526 private void sendClccForCall(Call call, boolean shouldLog) {
Santos Cordon33fff492014-09-25 14:57:01 -0700527 boolean isForeground = getCallsManager().getForegroundCall() == call;
528 int state = convertCallState(call.getState(), isForeground);
Santos Cordon88a4a602014-09-29 19:32:21 -0700529 boolean isPartOfConference = false;
Nancy Chen05a9e402014-09-26 14:14:32 -0700530
531 if (state == CALL_STATE_IDLE) {
532 return;
533 }
534
Santos Cordon88a4a602014-09-29 19:32:21 -0700535 Call conferenceCall = call.getParentCall();
536 if (conferenceCall != null) {
537 isPartOfConference = true;
538
539 // Run some alternative states for Conference-level merge/swap support.
540 // Basically, if call supports swapping or merging at the conference-level, then we need
541 // to expose the calls as having distinct states (ACTIVE vs HOLD) or the functionality
542 // won't show up on the bluetooth device.
543
544 // Before doing any special logic, ensure that we are dealing with an ACTIVE call and
545 // that the conference itself has a notion of the current "active" child call.
546 Call activeChild = conferenceCall.getConferenceLevelActiveCall();
547 if (state == CALL_STATE_ACTIVE && activeChild != null) {
548 // Reevaluate state if we can MERGE or if we can SWAP without previously having
549 // MERGED.
550 boolean shouldReevaluateState =
551 conferenceCall.can(PhoneCapabilities.MERGE_CONFERENCE) ||
552 (conferenceCall.can(PhoneCapabilities.SWAP_CONFERENCE) &&
553 !conferenceCall.wasConferencePreviouslyMerged());
554
555 if (shouldReevaluateState) {
556 isPartOfConference = false;
557 if (call == activeChild) {
558 state = CALL_STATE_ACTIVE;
559 } else {
560 // At this point we know there is an "active" child and we know that it is
561 // not this call, so set it to HELD instead.
562 state = CALL_STATE_HELD;
563 }
564 }
565 }
566 }
567
Nancy Chen05a9e402014-09-26 14:14:32 -0700568 int index = getIndexForCall(call);
569 int direction = call.isIncoming() ? 1 : 0;
Yorke Leefb70e1e2014-09-29 14:22:53 -0700570 final Uri addressUri;
571 if (call.getGatewayInfo() != null) {
572 addressUri = call.getGatewayInfo().getOriginalAddress();
573 } else {
574 addressUri = call.getHandle();
575 }
Santos Cordon33fff492014-09-25 14:57:01 -0700576 String address = addressUri == null ? null : addressUri.getSchemeSpecificPart();
577 int addressType = address == null ? -1 : PhoneNumberUtils.toaFromString(address);
578
Santos Cordon88a4a602014-09-29 19:32:21 -0700579 if (shouldLog) {
580 Log.i(this, "sending clcc for call %d, %d, %d, %b, %s, %d",
581 index, direction, state, isPartOfConference, Log.piiHandle(address),
582 addressType);
583 }
Santos Cordon33fff492014-09-25 14:57:01 -0700584 mBluetoothHeadset.clccResponse(
585 index, direction, state, 0, isPartOfConference, address, addressType);
586 }
587
588 private void sendClccEndMarker() {
589 // End marker is recognized with an index value of 0. All other parameters are ignored.
590 mBluetoothHeadset.clccResponse(0 /* index */, 0, 0, 0, false, null, 0);
591 }
592
593 /**
594 * Returns the caches index for the specified call. If no such index exists, then an index is
595 * given (smallest number starting from 1 that isn't already taken).
596 */
597 private int getIndexForCall(Call call) {
598 if (mClccIndexMap.containsKey(call)) {
599 return mClccIndexMap.get(call);
600 }
601
602 int i = 1; // Indexes for bluetooth clcc are 1-based.
603 while (mClccIndexMap.containsValue(i)) {
604 i++;
605 }
606
607 // NOTE: Indexes are removed in {@link #onCallRemoved}.
608 mClccIndexMap.put(call, i);
609 return i;
610 }
611
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700612 private void updateHeadsetWithCallState() {
613 CallsManager callsManager = getCallsManager();
614 Call activeCall = callsManager.getActiveCall();
615 Call ringingCall = callsManager.getRingingCall();
616 Call heldCall = callsManager.getHeldCall();
617
618 int bluetoothCallState = getBluetoothCallStateForUpdate();
619
620 String ringingAddress = null;
621 int ringingAddressType = 128;
622 if (ringingCall != null) {
623 ringingAddress = ringingCall.getHandle().getSchemeSpecificPart();
624 if (ringingAddress != null) {
625 ringingAddressType = PhoneNumberUtils.toaFromString(ringingAddress);
626 }
627 }
628 if (ringingAddress == null) {
629 ringingAddress = "";
630 }
631
Santos Cordona0b46122014-09-29 14:20:21 -0700632 int numActiveCalls = activeCall == null ? 0 : 1;
633 int numHeldCalls = heldCall == null ? 0 : 1;
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700634
Santos Cordon88a4a602014-09-29 19:32:21 -0700635 // For conference calls which support swapping the active call within the conference
636 // (namely CDMA calls) we need to expose that as a held call in order for the BT device
637 // to show "swap" and "merge" functionality.
638 if (activeCall != null && activeCall.isConference()) {
639 if (activeCall.can(PhoneCapabilities.SWAP_CONFERENCE)) {
640 // Indicate that BT device should show SWAP command by indicating that there is a
641 // call on hold, but only if the conference wasn't previously merged.
642 numHeldCalls = activeCall.wasConferencePreviouslyMerged() ? 0 : 1;
643 } else if (activeCall.can(PhoneCapabilities.MERGE_CONFERENCE)) {
644 numHeldCalls = 1; // Merge is available, so expose via numHeldCalls.
645 }
646 }
647
Santos Cordona0b46122014-09-29 14:20:21 -0700648 if (mBluetoothHeadset != null &&
649 (numActiveCalls != mNumActiveCalls ||
650 numHeldCalls != mNumHeldCalls ||
651 bluetoothCallState != mBluetoothCallState ||
652 !TextUtils.equals(ringingAddress, mRingingAddress) ||
653 ringingAddressType != mRingingAddressType)) {
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700654
Santos Cordona0b46122014-09-29 14:20:21 -0700655 // If the call is transitioning into the alerting state, send DIALING first.
656 // Some devices expect to see a DIALING state prior to seeing an ALERTING state
657 // so we need to send it first.
658 boolean sendDialingFirst = mBluetoothCallState != bluetoothCallState &&
659 bluetoothCallState == CALL_STATE_ALERTING;
660
661 mNumActiveCalls = numActiveCalls;
662 mNumHeldCalls = numHeldCalls;
663 mBluetoothCallState = bluetoothCallState;
664 mRingingAddress = ringingAddress;
665 mRingingAddressType = ringingAddressType;
666
667 if (sendDialingFirst) {
668 Log.i(TAG, "Sending dialing state");
669 mBluetoothHeadset.phoneStateChanged(
670 mNumActiveCalls,
671 mNumHeldCalls,
672 CALL_STATE_DIALING,
673 mRingingAddress,
674 mRingingAddressType);
675 }
676
677 Log.i(TAG, "updateHeadsetWithCallState " +
678 "numActive %s, " +
679 "numHeld %s, " +
680 "callState %s, " +
681 "ringing number %s, " +
682 "ringing type %s",
683 mNumActiveCalls,
684 mNumHeldCalls,
685 mBluetoothCallState,
686 Log.pii(mRingingAddress),
687 mRingingAddressType);
688
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700689 mBluetoothHeadset.phoneStateChanged(
Santos Cordona0b46122014-09-29 14:20:21 -0700690 mNumActiveCalls,
691 mNumHeldCalls,
692 mBluetoothCallState,
693 mRingingAddress,
694 mRingingAddressType);
Santos Cordon88a4a602014-09-29 19:32:21 -0700695
696 mHeadsetUpdatedRecently = true;
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700697 }
698 }
699
700 private int getBluetoothCallStateForUpdate() {
701 CallsManager callsManager = getCallsManager();
702 Call ringingCall = callsManager.getRingingCall();
Nancy Chen05a9e402014-09-26 14:14:32 -0700703 Call dialingCall = callsManager.getDialingCall();
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700704
705 //
706 // !! WARNING !!
707 // You will note that CALL_STATE_WAITING, CALL_STATE_HELD, and CALL_STATE_ACTIVE are not
708 // used in this version of the call state mappings. This is on purpose.
709 // phone_state_change() in btif_hf.c is not written to handle these states. Only with the
710 // listCalls*() method are WAITING and ACTIVE used.
711 // Using the unsupported states here caused problems with inconsistent state in some
712 // bluetooth devices (like not getting out of ringing state after answering a call).
713 //
714 int bluetoothCallState = CALL_STATE_IDLE;
715 if (ringingCall != null) {
716 bluetoothCallState = CALL_STATE_INCOMING;
Nancy Chen05a9e402014-09-26 14:14:32 -0700717 } else if (dialingCall != null) {
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700718 bluetoothCallState = CALL_STATE_ALERTING;
719 }
720 return bluetoothCallState;
721 }
722
Santos Cordon33fff492014-09-25 14:57:01 -0700723 private int convertCallState(int callState, boolean isForegroundCall) {
724 switch (callState) {
725 case CallState.NEW:
726 case CallState.ABORTED:
727 case CallState.DISCONNECTED:
Nancy Chen05a9e402014-09-26 14:14:32 -0700728 case CallState.CONNECTING:
729 case CallState.PRE_DIAL_WAIT:
Santos Cordon33fff492014-09-25 14:57:01 -0700730 return CALL_STATE_IDLE;
731
732 case CallState.ACTIVE:
733 return CALL_STATE_ACTIVE;
734
Santos Cordon33fff492014-09-25 14:57:01 -0700735 case CallState.DIALING:
736 // Yes, this is correctly returning ALERTING.
737 // "Dialing" for BT means that we have sent information to the service provider
738 // to place the call but there is no confirmation that the call is going through.
739 // When there finally is confirmation, the ringback is played which is referred to
740 // as an "alert" tone, thus, ALERTING.
741 // TODO: We should consider using the ALERTING terms in Telecom because that
742 // seems to be more industry-standard.
743 return CALL_STATE_ALERTING;
744
745 case CallState.ON_HOLD:
746 return CALL_STATE_HELD;
747
748 case CallState.RINGING:
749 if (isForegroundCall) {
750 return CALL_STATE_INCOMING;
751 } else {
752 return CALL_STATE_WAITING;
753 }
754 }
755 return CALL_STATE_IDLE;
756 }
757
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700758 private CallsManager getCallsManager() {
759 return CallsManager.getInstance();
760 }
761
762 /**
763 * Returns the best phone account to use for the given state of all calls.
764 * First, tries to return the phone account for the foreground call, second the default
765 * phone account for PhoneAccount.SCHEME_TEL.
766 */
767 private PhoneAccount getBestPhoneAccount() {
768 TelecomApp app = (TelecomApp) getApplication();
769 PhoneAccountRegistrar registry = app.getPhoneAccountRegistrar();
770 Call call = getCallsManager().getForegroundCall();
771
772 PhoneAccount account = null;
773 if (call != null) {
774 // First try to get the network name of the foreground call.
775 account = registry.getPhoneAccount(call.getTargetPhoneAccount());
776 }
777
778 if (account == null) {
779 // Second, Try to get the label for the default Phone Account.
780 account = registry.getPhoneAccount(
781 registry.getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL));
782 }
783 return account;
784 }
785}