blob: dd2e5dd7a3bf0aeb39c2f175d73d99ffcb0add8c [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
Santos Cordon68d1a6b2014-09-19 12:25:58 -070019import android.bluetooth.BluetoothAdapter;
20import android.bluetooth.BluetoothHeadset;
21import android.bluetooth.BluetoothProfile;
22import android.bluetooth.IBluetoothHeadsetPhone;
23import android.content.BroadcastReceiver;
24import android.content.Context;
25import android.content.Intent;
26import android.content.IntentFilter;
27import android.net.Uri;
Santos Cordon68d1a6b2014-09-19 12:25:58 -070028import android.os.IBinder;
Santos Cordon68d1a6b2014-09-19 12:25:58 -070029import android.os.RemoteException;
Ihab Awad07bc5ee2014-11-12 13:42:52 -080030import android.telecom.Connection;
Santos Cordon68d1a6b2014-09-19 12:25:58 -070031import android.telecom.PhoneAccount;
32import android.telephony.PhoneNumberUtils;
33import android.telephony.TelephonyManager;
34import android.text.TextUtils;
35
36import com.android.server.telecom.CallsManager.CallsManagerListener;
37
Santos Cordon33fff492014-09-25 14:57:01 -070038import java.util.Collection;
39import java.util.HashMap;
Santos Cordon68d1a6b2014-09-19 12:25:58 -070040import java.util.List;
Santos Cordon33fff492014-09-25 14:57:01 -070041import java.util.Map;
Santos Cordon68d1a6b2014-09-19 12:25:58 -070042
43/**
44 * Bluetooth headset manager for Telecom. This class shares the call state with the bluetooth device
45 * and accepts call-related commands to perform on behalf of the BT device.
46 */
Ihab Awad78a5e6b2015-02-06 10:13:05 -080047public final class BluetoothPhoneServiceImpl {
Santos Cordon68d1a6b2014-09-19 12:25:58 -070048
49 private static final String TAG = "BluetoothPhoneService";
50
Santos Cordon68d1a6b2014-09-19 12:25:58 -070051 // match up with bthf_call_state_t of bt_hf.h
52 private static final int CALL_STATE_ACTIVE = 0;
53 private static final int CALL_STATE_HELD = 1;
54 private static final int CALL_STATE_DIALING = 2;
55 private static final int CALL_STATE_ALERTING = 3;
56 private static final int CALL_STATE_INCOMING = 4;
57 private static final int CALL_STATE_WAITING = 5;
58 private static final int CALL_STATE_IDLE = 6;
59
60 // match up with bthf_call_state_t of bt_hf.h
61 // Terminate all held or set UDUB("busy") to a waiting call
62 private static final int CHLD_TYPE_RELEASEHELD = 0;
63 // Terminate all active calls and accepts a waiting/held call
64 private static final int CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD = 1;
65 // Hold all active calls and accepts a waiting/held call
66 private static final int CHLD_TYPE_HOLDACTIVE_ACCEPTHELD = 2;
67 // Add all held calls to a conference
68 private static final int CHLD_TYPE_ADDHELDTOCONF = 3;
69
Santos Cordona0b46122014-09-29 14:20:21 -070070 private int mNumActiveCalls = 0;
71 private int mNumHeldCalls = 0;
72 private int mBluetoothCallState = CALL_STATE_IDLE;
73 private String mRingingAddress = null;
74 private int mRingingAddressType = 0;
Santos Cordonc0ca76e2014-10-21 15:54:19 -070075 private Call mOldHeldCall = null;
Santos Cordona0b46122014-09-29 14:20:21 -070076
Santos Cordon68d1a6b2014-09-19 12:25:58 -070077 /**
78 * Binder implementation of IBluetoothHeadsetPhone. Implements the command interface that the
79 * bluetooth headset code uses to control call.
80 */
81 private final IBluetoothHeadsetPhone.Stub mBinder = new IBluetoothHeadsetPhone.Stub() {
82 @Override
83 public boolean answerCall() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -070084 synchronized (mLock) {
85 enforceModifyPermission();
86 Log.i(TAG, "BT - answering call");
87 Call call = mCallsManager.getRingingCall();
88 if (call != null) {
89 mCallsManager.answerCall(call, 0);
90 return true;
91 }
92 return false;
93 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -070094 }
95
96 @Override
97 public boolean hangupCall() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -070098 synchronized (mLock) {
99 enforceModifyPermission();
100 Log.i(TAG, "BT - hanging up call");
101 Call call = mCallsManager.getForegroundCall();
102 if (call != null) {
103 mCallsManager.disconnectCall(call);
104 return true;
105 }
106 return false;
107 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700108 }
109
110 @Override
111 public boolean sendDtmf(int dtmf) throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700112 synchronized (mLock) {
113 enforceModifyPermission();
114 Log.i(TAG, "BT - sendDtmf %c", Log.DEBUG ? dtmf : '.');
115 Call call = mCallsManager.getForegroundCall();
116 if (call != null) {
117 // TODO: Consider making this a queue instead of starting/stopping
118 // in quick succession.
119 mCallsManager.playDtmfTone(call, (char) dtmf);
120 mCallsManager.stopDtmfTone(call);
121 return true;
122 }
123 return false;
124 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700125 }
126
127 @Override
128 public String getNetworkOperator() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700129 synchronized (mLock) {
130 enforceModifyPermission();
131 Log.i(TAG, "getNetworkOperator");
132 PhoneAccount account = getBestPhoneAccount();
133 if (account != null) {
134 return account.getLabel().toString();
135 } else {
136 // Finally, just get the network name from telephony.
137 return TelephonyManager.from(mContext)
138 .getNetworkOperatorName();
139 }
140 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700141 }
142
143 @Override
144 public String getSubscriberNumber() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700145 synchronized (mLock) {
146 enforceModifyPermission();
147 Log.i(TAG, "getSubscriberNumber");
148 String address = null;
149 PhoneAccount account = getBestPhoneAccount();
150 if (account != null) {
151 Uri addressUri = account.getAddress();
152 if (addressUri != null) {
153 address = addressUri.getSchemeSpecificPart();
154 }
155 }
156 if (TextUtils.isEmpty(address)) {
157 address = TelephonyManager.from(mContext).getLine1Number();
158 }
159 return address;
160 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700161 }
162
163 @Override
164 public boolean listCurrentCalls() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700165 synchronized (mLock) {
166 enforceModifyPermission();
167 // only log if it is after we recently updated the headset state or else it can clog
168 // the android log since this can be queried every second.
169 boolean logQuery = mHeadsetUpdatedRecently;
170 mHeadsetUpdatedRecently = false;
Santos Cordon88a4a602014-09-29 19:32:21 -0700171
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700172 if (logQuery) {
173 Log.i(TAG, "listcurrentCalls");
174 }
175
176 sendListOfCalls(logQuery);
177 return true;
Santos Cordon88a4a602014-09-29 19:32:21 -0700178 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700179 }
180
181 @Override
182 public boolean queryPhoneState() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700183 synchronized (mLock) {
184 enforceModifyPermission();
185 Log.i(TAG, "queryPhoneState");
186 updateHeadsetWithCallState(true /* force */);
187 return true;
188 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700189 }
190
191 @Override
192 public boolean processChld(int chld) throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700193 synchronized (mLock) {
194 enforceModifyPermission();
195 Log.i(TAG, "processChld %d", chld);
196 return BluetoothPhoneServiceImpl.this.processChld(chld);
197 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700198 }
199
200 @Override
201 public void updateBtHandsfreeAfterRadioTechnologyChange() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700202 Log.d(TAG, "RAT change - deprecated");
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700203 // deprecated
204 }
205
206 @Override
207 public void cdmaSetSecondCallState(boolean state) throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700208 Log.d(TAG, "cdma 1 - deprecated");
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700209 // deprecated
210 }
211
212 @Override
213 public void cdmaSwapSecondCallState() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700214 Log.d(TAG, "cdma 2 - deprecated");
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700215 // deprecated
216 }
217 };
218
219 /**
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700220 * Listens to call changes from the CallsManager and calls into methods to update the bluetooth
221 * headset with the new states.
222 */
223 private CallsManagerListener mCallsManagerListener = new CallsManagerListenerBase() {
224 @Override
225 public void onCallAdded(Call call) {
Tyler Gunncd685e52014-10-10 11:48:25 -0700226 updateHeadsetWithCallState(false /* force */);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700227 }
228
229 @Override
230 public void onCallRemoved(Call call) {
Santos Cordon33fff492014-09-25 14:57:01 -0700231 mClccIndexMap.remove(call);
Tyler Gunncd685e52014-10-10 11:48:25 -0700232 updateHeadsetWithCallState(false /* force */);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700233 }
234
235 @Override
236 public void onCallStateChanged(Call call, int oldState, int newState) {
Yorke Lee720bcbe2014-10-22 18:09:15 -0700237 // If a call is being put on hold because of a new connecting call, ignore the
238 // CONNECTING since the BT state update needs to send out the numHeld = 1 + dialing
239 // state atomically.
240 // When the call later transitions to DIALING/DISCONNECTED we will then send out the
241 // aggregated update.
242 if (oldState == CallState.ACTIVE && newState == CallState.ON_HOLD) {
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800243 for (Call otherCall : mCallsManager.getCalls()) {
Yorke Lee720bcbe2014-10-22 18:09:15 -0700244 if (otherCall.getState() == CallState.CONNECTING) {
245 return;
246 }
247 }
248 }
249
250 // To have an active call and another dialing at the same time is an invalid BT
251 // state. We can assume that the active call will be automatically held which will
252 // send another update at which point we will be in the right state.
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800253 if (mCallsManager.getActiveCall() != null
Yorke Lee720bcbe2014-10-22 18:09:15 -0700254 && oldState == CallState.CONNECTING && newState == CallState.DIALING) {
255 return;
256 }
Tyler Gunncd685e52014-10-10 11:48:25 -0700257 updateHeadsetWithCallState(false /* force */);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700258 }
259
260 @Override
261 public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
Yorke Lee720bcbe2014-10-22 18:09:15 -0700262 // The BluetoothPhoneService does not need to respond to changes in foreground calls,
263 // which are always accompanied by call state changes anyway.
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700264 }
265
266 @Override
267 public void onIsConferencedChanged(Call call) {
Yorke Lee720bcbe2014-10-22 18:09:15 -0700268 /*
269 * Filter certain onIsConferencedChanged callbacks. Unfortunately this needs to be done
270 * because conference change events are not atomic and multiple callbacks get fired
271 * when two calls are conferenced together. This confuses updateHeadsetWithCallState
272 * if it runs in the middle of two calls being conferenced and can cause spurious and
273 * incorrect headset state updates. One of the scenarios is described below for CDMA
274 * conference calls.
275 *
276 * 1) Call 1 and Call 2 are being merged into conference Call 3.
277 * 2) Call 1 has its parent set to Call 3, but Call 2 does not have a parent yet.
278 * 3) updateHeadsetWithCallState now thinks that there are two active calls (Call 2 and
279 * Call 3) when there is actually only one active call (Call 3).
280 */
281 if (call.getParentCall() != null) {
282 // If this call is newly conferenced, ignore the callback. We only care about the
283 // one sent for the parent conference call.
284 Log.d(this, "Ignoring onIsConferenceChanged from child call with new parent");
285 return;
286 }
287 if (call.getChildCalls().size() == 1) {
288 // If this is a parent call with only one child, ignore the callback as well since
289 // the minimum number of child calls to start a conference call is 2. We expect
290 // this to be called again when the parent call has another child call added.
291 Log.d(this, "Ignoring onIsConferenceChanged from parent with only one child call");
292 return;
293 }
Tyler Gunncd685e52014-10-10 11:48:25 -0700294 updateHeadsetWithCallState(false /* force */);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700295 }
296 };
297
298 /**
299 * Listens to connections and disconnections of bluetooth headsets. We need to save the current
300 * bluetooth headset so that we know where to send call updates.
301 */
302 private BluetoothProfile.ServiceListener mProfileListener =
303 new BluetoothProfile.ServiceListener() {
304 @Override
305 public void onServiceConnected(int profile, BluetoothProfile proxy) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700306 synchronized (mLock) {
307 mBluetoothHeadset = (BluetoothHeadset) proxy;
308 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700309 }
310
311 @Override
312 public void onServiceDisconnected(int profile) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700313 synchronized (mLock) {
314 mBluetoothHeadset = null;
315 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700316 }
317 };
318
319 /**
320 * Receives events for global state changes of the bluetooth adapter.
321 */
322 private final BroadcastReceiver mBluetoothAdapterReceiver = new BroadcastReceiver() {
323 @Override
324 public void onReceive(Context context, Intent intent) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700325 synchronized (mLock) {
326 int state = intent
327 .getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
328 Log.d(TAG, "Bluetooth Adapter state: %d", state);
329 if (state == BluetoothAdapter.STATE_ON) {
330 try {
331 mBinder.queryPhoneState();
332 } catch (RemoteException e) {
333 // Remote exception not expected
334 }
335 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700336 }
337 }
338 };
339
340 private BluetoothAdapter mBluetoothAdapter;
341 private BluetoothHeadset mBluetoothHeadset;
342
Santos Cordon33fff492014-09-25 14:57:01 -0700343 // A map from Calls to indexes used to identify calls for CLCC (C* List Current Calls).
344 private Map<Call, Integer> mClccIndexMap = new HashMap<>();
345
Santos Cordon88a4a602014-09-29 19:32:21 -0700346 private boolean mHeadsetUpdatedRecently = false;
347
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700348 private final Context mContext;
349 private final TelecomSystem.SyncRoot mLock;
350 private final CallsManager mCallsManager;
351 private final PhoneAccountRegistrar mPhoneAccountRegistrar;
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700352
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800353 public IBinder getBinder() {
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700354 return mBinder;
355 }
356
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800357 public BluetoothPhoneServiceImpl(
358 Context context,
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700359 TelecomSystem.SyncRoot lock,
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800360 CallsManager callsManager,
361 PhoneAccountRegistrar phoneAccountRegistrar) {
362 Log.d(this, "onCreate");
363
364 mContext = context;
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700365 mLock = lock;
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800366 mCallsManager = callsManager;
367 mPhoneAccountRegistrar = phoneAccountRegistrar;
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700368
369 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
370 if (mBluetoothAdapter == null) {
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800371 Log.d(this, "BluetoothPhoneService shutting down, no BT Adapter found.");
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700372 return;
373 }
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800374 mBluetoothAdapter.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700375
376 IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800377 context.registerReceiver(mBluetoothAdapterReceiver, intentFilter);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700378
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800379 mCallsManager.addListener(mCallsManagerListener);
Tyler Gunncd685e52014-10-10 11:48:25 -0700380 updateHeadsetWithCallState(false /* force */);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700381 }
382
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700383 private boolean processChld(int chld) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700384 Call activeCall = mCallsManager.getActiveCall();
385 Call ringingCall = mCallsManager.getRingingCall();
386 Call heldCall = mCallsManager.getHeldCall();
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700387
Santos Cordon66fe8822014-10-10 16:10:58 -0700388 // TODO: Keeping as Log.i for now. Move to Log.d after L release if BT proves stable.
389 Log.i(TAG, "Active: %s\nRinging: %s\nHeld: %s", activeCall, ringingCall, heldCall);
Santos Cordon88a4a602014-09-29 19:32:21 -0700390
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700391 if (chld == CHLD_TYPE_RELEASEHELD) {
392 if (ringingCall != null) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700393 mCallsManager.rejectCall(ringingCall, false, null);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700394 return true;
395 } else if (heldCall != null) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700396 mCallsManager.disconnectCall(heldCall);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700397 return true;
398 }
399 } else if (chld == CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD) {
400 if (activeCall != null) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700401 mCallsManager.disconnectCall(activeCall);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700402 if (ringingCall != null) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700403 mCallsManager.answerCall(ringingCall, 0);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700404 } else if (heldCall != null) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700405 mCallsManager.unholdCall(heldCall);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700406 }
407 return true;
408 }
409 } else if (chld == CHLD_TYPE_HOLDACTIVE_ACCEPTHELD) {
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800410 if (activeCall != null && activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
Santos Cordon88a4a602014-09-29 19:32:21 -0700411 activeCall.swapConference();
412 return true;
413 } else if (ringingCall != null) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700414 mCallsManager.answerCall(ringingCall, 0);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700415 return true;
416 } else if (heldCall != null) {
417 // CallsManager will hold any active calls when unhold() is called on a
418 // currently-held call.
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700419 mCallsManager.unholdCall(heldCall);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700420 return true;
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800421 } else if (activeCall != null && activeCall.can(Connection.CAPABILITY_HOLD)) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700422 mCallsManager.holdCall(activeCall);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700423 return true;
424 }
425 } else if (chld == CHLD_TYPE_ADDHELDTOCONF) {
426 if (activeCall != null) {
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800427 if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
Santos Cordon88a4a602014-09-29 19:32:21 -0700428 activeCall.mergeConference();
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700429 return true;
Santos Cordon88a4a602014-09-29 19:32:21 -0700430 } else {
431 List<Call> conferenceable = activeCall.getConferenceableCalls();
432 if (!conferenceable.isEmpty()) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700433 mCallsManager.conference(activeCall, conferenceable.get(0));
Santos Cordon88a4a602014-09-29 19:32:21 -0700434 return true;
435 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700436 }
437 }
438 }
439 return false;
440 }
441
442 private void enforceModifyPermission() {
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800443 mContext.enforceCallingOrSelfPermission(
444 android.Manifest.permission.MODIFY_PHONE_STATE, null);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700445 }
446
Santos Cordon88a4a602014-09-29 19:32:21 -0700447 private void sendListOfCalls(boolean shouldLog) {
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800448 Collection<Call> mCalls = mCallsManager.getCalls();
Santos Cordon33fff492014-09-25 14:57:01 -0700449 for (Call call : mCalls) {
450 // We don't send the parent conference call to the bluetooth device.
451 if (!call.isConference()) {
Santos Cordon88a4a602014-09-29 19:32:21 -0700452 sendClccForCall(call, shouldLog);
Santos Cordon33fff492014-09-25 14:57:01 -0700453 }
454 }
455 sendClccEndMarker();
456 }
457
458 /**
459 * Sends a single clcc (C* List Current Calls) event for the specified call.
460 */
Santos Cordon88a4a602014-09-29 19:32:21 -0700461 private void sendClccForCall(Call call, boolean shouldLog) {
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800462 boolean isForeground = mCallsManager.getForegroundCall() == call;
Santos Cordon33fff492014-09-25 14:57:01 -0700463 int state = convertCallState(call.getState(), isForeground);
Santos Cordon88a4a602014-09-29 19:32:21 -0700464 boolean isPartOfConference = false;
Nancy Chen05a9e402014-09-26 14:14:32 -0700465
466 if (state == CALL_STATE_IDLE) {
467 return;
468 }
469
Santos Cordon88a4a602014-09-29 19:32:21 -0700470 Call conferenceCall = call.getParentCall();
471 if (conferenceCall != null) {
472 isPartOfConference = true;
473
474 // Run some alternative states for Conference-level merge/swap support.
475 // Basically, if call supports swapping or merging at the conference-level, then we need
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800476 // to expose the calls as having distinct states (ACTIVE vs CAPABILITY_HOLD) or the
477 // functionality won't show up on the bluetooth device.
Santos Cordon88a4a602014-09-29 19:32:21 -0700478
479 // Before doing any special logic, ensure that we are dealing with an ACTIVE call and
480 // that the conference itself has a notion of the current "active" child call.
481 Call activeChild = conferenceCall.getConferenceLevelActiveCall();
482 if (state == CALL_STATE_ACTIVE && activeChild != null) {
483 // Reevaluate state if we can MERGE or if we can SWAP without previously having
484 // MERGED.
485 boolean shouldReevaluateState =
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800486 conferenceCall.can(Connection.CAPABILITY_MERGE_CONFERENCE) ||
487 (conferenceCall.can(Connection.CAPABILITY_SWAP_CONFERENCE) &&
Santos Cordon88a4a602014-09-29 19:32:21 -0700488 !conferenceCall.wasConferencePreviouslyMerged());
489
490 if (shouldReevaluateState) {
491 isPartOfConference = false;
492 if (call == activeChild) {
493 state = CALL_STATE_ACTIVE;
494 } else {
495 // At this point we know there is an "active" child and we know that it is
496 // not this call, so set it to HELD instead.
497 state = CALL_STATE_HELD;
498 }
499 }
500 }
501 }
502
Nancy Chen05a9e402014-09-26 14:14:32 -0700503 int index = getIndexForCall(call);
504 int direction = call.isIncoming() ? 1 : 0;
Yorke Leefb70e1e2014-09-29 14:22:53 -0700505 final Uri addressUri;
506 if (call.getGatewayInfo() != null) {
507 addressUri = call.getGatewayInfo().getOriginalAddress();
508 } else {
509 addressUri = call.getHandle();
510 }
Santos Cordon33fff492014-09-25 14:57:01 -0700511 String address = addressUri == null ? null : addressUri.getSchemeSpecificPart();
512 int addressType = address == null ? -1 : PhoneNumberUtils.toaFromString(address);
513
Santos Cordon88a4a602014-09-29 19:32:21 -0700514 if (shouldLog) {
515 Log.i(this, "sending clcc for call %d, %d, %d, %b, %s, %d",
516 index, direction, state, isPartOfConference, Log.piiHandle(address),
517 addressType);
518 }
Yorke Leee241cd62014-10-09 13:41:19 -0700519
520 if (mBluetoothHeadset != null) {
521 mBluetoothHeadset.clccResponse(
522 index, direction, state, 0, isPartOfConference, address, addressType);
523 }
Santos Cordon33fff492014-09-25 14:57:01 -0700524 }
525
526 private void sendClccEndMarker() {
527 // End marker is recognized with an index value of 0. All other parameters are ignored.
Yorke Leee241cd62014-10-09 13:41:19 -0700528 if (mBluetoothHeadset != null) {
529 mBluetoothHeadset.clccResponse(0 /* index */, 0, 0, 0, false, null, 0);
530 }
Santos Cordon33fff492014-09-25 14:57:01 -0700531 }
532
533 /**
534 * Returns the caches index for the specified call. If no such index exists, then an index is
535 * given (smallest number starting from 1 that isn't already taken).
536 */
537 private int getIndexForCall(Call call) {
538 if (mClccIndexMap.containsKey(call)) {
539 return mClccIndexMap.get(call);
540 }
541
542 int i = 1; // Indexes for bluetooth clcc are 1-based.
543 while (mClccIndexMap.containsValue(i)) {
544 i++;
545 }
546
547 // NOTE: Indexes are removed in {@link #onCallRemoved}.
548 mClccIndexMap.put(call, i);
549 return i;
550 }
551
Tyler Gunncd685e52014-10-10 11:48:25 -0700552 /**
553 * Sends an update of the current call state to the current Headset.
554 *
555 * @param force {@code true} if the headset state should be sent regardless if no changes to the
556 * state have occurred, {@code false} if the state should only be sent if the state has
557 * changed.
558 */
559 private void updateHeadsetWithCallState(boolean force) {
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800560 CallsManager callsManager = mCallsManager;
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700561 Call activeCall = mCallsManager.getActiveCall();
562 Call ringingCall = mCallsManager.getRingingCall();
563 Call heldCall = mCallsManager.getHeldCall();
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700564
565 int bluetoothCallState = getBluetoothCallStateForUpdate();
566
567 String ringingAddress = null;
568 int ringingAddressType = 128;
Santos Cordonecaaeac2014-11-05 20:59:04 -0800569 if (ringingCall != null && ringingCall.getHandle() != null) {
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700570 ringingAddress = ringingCall.getHandle().getSchemeSpecificPart();
571 if (ringingAddress != null) {
572 ringingAddressType = PhoneNumberUtils.toaFromString(ringingAddress);
573 }
574 }
575 if (ringingAddress == null) {
576 ringingAddress = "";
577 }
578
Santos Cordona0b46122014-09-29 14:20:21 -0700579 int numActiveCalls = activeCall == null ? 0 : 1;
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700580 int numHeldCalls = mCallsManager.getNumHeldCalls();
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700581
Santos Cordon88a4a602014-09-29 19:32:21 -0700582 // For conference calls which support swapping the active call within the conference
583 // (namely CDMA calls) we need to expose that as a held call in order for the BT device
584 // to show "swap" and "merge" functionality.
Yorke Lee720bcbe2014-10-22 18:09:15 -0700585 boolean ignoreHeldCallChange = false;
Santos Cordon88a4a602014-09-29 19:32:21 -0700586 if (activeCall != null && activeCall.isConference()) {
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800587 if (activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
Santos Cordon88a4a602014-09-29 19:32:21 -0700588 // Indicate that BT device should show SWAP command by indicating that there is a
589 // call on hold, but only if the conference wasn't previously merged.
590 numHeldCalls = activeCall.wasConferencePreviouslyMerged() ? 0 : 1;
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800591 } else if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
Santos Cordon88a4a602014-09-29 19:32:21 -0700592 numHeldCalls = 1; // Merge is available, so expose via numHeldCalls.
593 }
Yorke Lee720bcbe2014-10-22 18:09:15 -0700594
595 for (Call childCall : activeCall.getChildCalls()) {
596 // Held call has changed due to it being combined into a CDMA conference. Keep
597 // track of this and ignore any future update since it doesn't really count as
598 // a call change.
599 if (mOldHeldCall == childCall) {
600 ignoreHeldCallChange = true;
601 break;
602 }
603 }
Santos Cordon88a4a602014-09-29 19:32:21 -0700604 }
605
Santos Cordona0b46122014-09-29 14:20:21 -0700606 if (mBluetoothHeadset != null &&
607 (numActiveCalls != mNumActiveCalls ||
608 numHeldCalls != mNumHeldCalls ||
609 bluetoothCallState != mBluetoothCallState ||
610 !TextUtils.equals(ringingAddress, mRingingAddress) ||
Tyler Gunncd685e52014-10-10 11:48:25 -0700611 ringingAddressType != mRingingAddressType ||
Yorke Lee720bcbe2014-10-22 18:09:15 -0700612 (heldCall != mOldHeldCall && !ignoreHeldCallChange) ||
Tyler Gunncd685e52014-10-10 11:48:25 -0700613 force)) {
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700614
Santos Cordona0b46122014-09-29 14:20:21 -0700615 // If the call is transitioning into the alerting state, send DIALING first.
616 // Some devices expect to see a DIALING state prior to seeing an ALERTING state
617 // so we need to send it first.
618 boolean sendDialingFirst = mBluetoothCallState != bluetoothCallState &&
619 bluetoothCallState == CALL_STATE_ALERTING;
620
Santos Cordonc0ca76e2014-10-21 15:54:19 -0700621 mOldHeldCall = heldCall;
Santos Cordona0b46122014-09-29 14:20:21 -0700622 mNumActiveCalls = numActiveCalls;
623 mNumHeldCalls = numHeldCalls;
624 mBluetoothCallState = bluetoothCallState;
625 mRingingAddress = ringingAddress;
626 mRingingAddressType = ringingAddressType;
627
628 if (sendDialingFirst) {
Yorke Lee720bcbe2014-10-22 18:09:15 -0700629 // Log in full to make logs easier to debug.
630 Log.i(TAG, "updateHeadsetWithCallState " +
631 "numActive %s, " +
632 "numHeld %s, " +
633 "callState %s, " +
634 "ringing number %s, " +
635 "ringing type %s",
636 mNumActiveCalls,
637 mNumHeldCalls,
638 CALL_STATE_DIALING,
639 Log.pii(mRingingAddress),
640 mRingingAddressType);
Santos Cordona0b46122014-09-29 14:20:21 -0700641 mBluetoothHeadset.phoneStateChanged(
642 mNumActiveCalls,
643 mNumHeldCalls,
644 CALL_STATE_DIALING,
645 mRingingAddress,
646 mRingingAddressType);
647 }
648
649 Log.i(TAG, "updateHeadsetWithCallState " +
650 "numActive %s, " +
651 "numHeld %s, " +
652 "callState %s, " +
653 "ringing number %s, " +
654 "ringing type %s",
655 mNumActiveCalls,
656 mNumHeldCalls,
657 mBluetoothCallState,
658 Log.pii(mRingingAddress),
659 mRingingAddressType);
660
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700661 mBluetoothHeadset.phoneStateChanged(
Santos Cordona0b46122014-09-29 14:20:21 -0700662 mNumActiveCalls,
663 mNumHeldCalls,
664 mBluetoothCallState,
665 mRingingAddress,
666 mRingingAddressType);
Santos Cordon88a4a602014-09-29 19:32:21 -0700667
668 mHeadsetUpdatedRecently = true;
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700669 }
670 }
671
672 private int getBluetoothCallStateForUpdate() {
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800673 CallsManager callsManager = mCallsManager;
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700674 Call ringingCall = mCallsManager.getRingingCall();
675 Call dialingCall = mCallsManager.getDialingCall();
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700676
677 //
678 // !! WARNING !!
679 // You will note that CALL_STATE_WAITING, CALL_STATE_HELD, and CALL_STATE_ACTIVE are not
680 // used in this version of the call state mappings. This is on purpose.
681 // phone_state_change() in btif_hf.c is not written to handle these states. Only with the
682 // listCalls*() method are WAITING and ACTIVE used.
683 // Using the unsupported states here caused problems with inconsistent state in some
684 // bluetooth devices (like not getting out of ringing state after answering a call).
685 //
686 int bluetoothCallState = CALL_STATE_IDLE;
687 if (ringingCall != null) {
688 bluetoothCallState = CALL_STATE_INCOMING;
Nancy Chen05a9e402014-09-26 14:14:32 -0700689 } else if (dialingCall != null) {
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700690 bluetoothCallState = CALL_STATE_ALERTING;
691 }
692 return bluetoothCallState;
693 }
694
Santos Cordon33fff492014-09-25 14:57:01 -0700695 private int convertCallState(int callState, boolean isForegroundCall) {
696 switch (callState) {
697 case CallState.NEW:
698 case CallState.ABORTED:
699 case CallState.DISCONNECTED:
Nancy Chen05a9e402014-09-26 14:14:32 -0700700 case CallState.CONNECTING:
Santos Cordon92694512015-04-23 14:47:28 -0700701 case CallState.SELECT_PHONE_ACCOUNT:
Santos Cordon33fff492014-09-25 14:57:01 -0700702 return CALL_STATE_IDLE;
703
704 case CallState.ACTIVE:
705 return CALL_STATE_ACTIVE;
706
Santos Cordon33fff492014-09-25 14:57:01 -0700707 case CallState.DIALING:
708 // Yes, this is correctly returning ALERTING.
709 // "Dialing" for BT means that we have sent information to the service provider
710 // to place the call but there is no confirmation that the call is going through.
711 // When there finally is confirmation, the ringback is played which is referred to
712 // as an "alert" tone, thus, ALERTING.
713 // TODO: We should consider using the ALERTING terms in Telecom because that
714 // seems to be more industry-standard.
715 return CALL_STATE_ALERTING;
716
717 case CallState.ON_HOLD:
718 return CALL_STATE_HELD;
719
720 case CallState.RINGING:
721 if (isForegroundCall) {
722 return CALL_STATE_INCOMING;
723 } else {
724 return CALL_STATE_WAITING;
725 }
726 }
727 return CALL_STATE_IDLE;
728 }
729
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700730 /**
731 * Returns the best phone account to use for the given state of all calls.
732 * First, tries to return the phone account for the foreground call, second the default
733 * phone account for PhoneAccount.SCHEME_TEL.
734 */
735 private PhoneAccount getBestPhoneAccount() {
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800736 if (mPhoneAccountRegistrar == null) {
Santos Cordon0b5cb4d2014-12-02 02:40:10 -0800737 return null;
738 }
739
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800740 Call call = mCallsManager.getForegroundCall();
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700741
742 PhoneAccount account = null;
743 if (call != null) {
744 // First try to get the network name of the foreground call.
Santos Cordon6a212642015-05-08 16:35:23 -0700745 account = mPhoneAccountRegistrar.getPhoneAccountCheckCallingUser(
746 call.getTargetPhoneAccount());
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700747 }
748
749 if (account == null) {
750 // Second, Try to get the label for the default Phone Account.
Santos Cordon6a212642015-05-08 16:35:23 -0700751 account = mPhoneAccountRegistrar.getPhoneAccountCheckCallingUser(
752 mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(
753 PhoneAccount.SCHEME_TEL));
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700754 }
755 return account;
756 }
757}