blob: d0fcfa115292e94114a7ab055a4b74227c0d0dec [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 Cordonebf2d0f2015-05-15 10:28:29 -070028import android.os.Binder;
Santos Cordon68d1a6b2014-09-19 12:25:58 -070029import android.os.IBinder;
Santos Cordon68d1a6b2014-09-19 12:25:58 -070030import android.os.RemoteException;
Ihab Awad07bc5ee2014-11-12 13:42:52 -080031import android.telecom.Connection;
Brad Ebinger953e1af2016-10-05 15:45:22 -070032import android.telecom.Log;
Santos Cordon68d1a6b2014-09-19 12:25:58 -070033import android.telecom.PhoneAccount;
Hall Liu3cab92a2016-07-19 14:00:05 -070034import android.telecom.VideoProfile;
Santos Cordon68d1a6b2014-09-19 12:25:58 -070035import android.telephony.PhoneNumberUtils;
36import android.telephony.TelephonyManager;
37import android.text.TextUtils;
38
Brad Ebinger53855132015-10-30 10:58:19 -070039import com.android.internal.annotations.VisibleForTesting;
Santos Cordon68d1a6b2014-09-19 12:25:58 -070040import 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 */
Hall Liub3979ee2015-11-11 16:21:25 -080051public class BluetoothPhoneServiceImpl {
52
53 public interface BluetoothPhoneServiceImplFactory {
54 BluetoothPhoneServiceImpl makeBluetoothPhoneServiceImpl(Context context,
55 TelecomSystem.SyncRoot lock, CallsManager callsManager,
56 PhoneAccountRegistrar phoneAccountRegistrar);
57 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -070058
59 private static final String TAG = "BluetoothPhoneService";
60
Santos Cordon68d1a6b2014-09-19 12:25:58 -070061 // match up with bthf_call_state_t of bt_hf.h
62 private static final int CALL_STATE_ACTIVE = 0;
63 private static final int CALL_STATE_HELD = 1;
64 private static final int CALL_STATE_DIALING = 2;
65 private static final int CALL_STATE_ALERTING = 3;
66 private static final int CALL_STATE_INCOMING = 4;
67 private static final int CALL_STATE_WAITING = 5;
68 private static final int CALL_STATE_IDLE = 6;
Honggangb3ccbb32016-05-31 14:46:22 +080069 private static final int CALL_STATE_DISCONNECTED = 7;
Santos Cordon68d1a6b2014-09-19 12:25:58 -070070
71 // match up with bthf_call_state_t of bt_hf.h
72 // Terminate all held or set UDUB("busy") to a waiting call
73 private static final int CHLD_TYPE_RELEASEHELD = 0;
74 // Terminate all active calls and accepts a waiting/held call
75 private static final int CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD = 1;
76 // Hold all active calls and accepts a waiting/held call
77 private static final int CHLD_TYPE_HOLDACTIVE_ACCEPTHELD = 2;
78 // Add all held calls to a conference
79 private static final int CHLD_TYPE_ADDHELDTOCONF = 3;
80
Santos Cordona0b46122014-09-29 14:20:21 -070081 private int mNumActiveCalls = 0;
82 private int mNumHeldCalls = 0;
Hall Liu6a89ba22017-07-31 19:04:48 -070083 private int mNumChildrenOfActiveCall = 0;
Santos Cordona0b46122014-09-29 14:20:21 -070084 private int mBluetoothCallState = CALL_STATE_IDLE;
85 private String mRingingAddress = null;
86 private int mRingingAddressType = 0;
Santos Cordonc0ca76e2014-10-21 15:54:19 -070087 private Call mOldHeldCall = null;
Honggangb3ccbb32016-05-31 14:46:22 +080088 private boolean mIsDisconnectedTonePlaying = false;
Santos Cordona0b46122014-09-29 14:20:21 -070089
Santos Cordon68d1a6b2014-09-19 12:25:58 -070090 /**
91 * Binder implementation of IBluetoothHeadsetPhone. Implements the command interface that the
92 * bluetooth headset code uses to control call.
93 */
Brad Ebinger53855132015-10-30 10:58:19 -070094 @VisibleForTesting
95 public final IBluetoothHeadsetPhone.Stub mBinder = new IBluetoothHeadsetPhone.Stub() {
Santos Cordon68d1a6b2014-09-19 12:25:58 -070096 @Override
97 public boolean answerCall() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -070098 synchronized (mLock) {
99 enforceModifyPermission();
Brad Ebinger3165d502015-12-15 17:22:29 -0800100 Log.startSession("BPSI.aC");
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700101 long token = Binder.clearCallingIdentity();
102 try {
103 Log.i(TAG, "BT - answering call");
104 Call call = mCallsManager.getRingingCall();
105 if (call != null) {
Hall Liu3cab92a2016-07-19 14:00:05 -0700106 mCallsManager.answerCall(call, VideoProfile.STATE_AUDIO_ONLY);
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700107 return true;
108 }
109 return false;
110 } finally {
111 Binder.restoreCallingIdentity(token);
Brad Ebinger3165d502015-12-15 17:22:29 -0800112 Log.endSession();
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700113 }
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700114
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700115 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700116 }
117
118 @Override
119 public boolean hangupCall() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700120 synchronized (mLock) {
121 enforceModifyPermission();
Brad Ebinger3165d502015-12-15 17:22:29 -0800122 Log.startSession("BPSI.hC");
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700123 long token = Binder.clearCallingIdentity();
124 try {
125 Log.i(TAG, "BT - hanging up call");
126 Call call = mCallsManager.getForegroundCall();
127 if (call != null) {
128 mCallsManager.disconnectCall(call);
129 return true;
130 }
131 return false;
132 } finally {
133 Binder.restoreCallingIdentity(token);
Brad Ebinger3165d502015-12-15 17:22:29 -0800134 Log.endSession();
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700135 }
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700136 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700137 }
138
139 @Override
140 public boolean sendDtmf(int dtmf) throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700141 synchronized (mLock) {
142 enforceModifyPermission();
Brad Ebinger3165d502015-12-15 17:22:29 -0800143 Log.startSession("BPSI.sD");
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700144 long token = Binder.clearCallingIdentity();
145 try {
146 Log.i(TAG, "BT - sendDtmf %c", Log.DEBUG ? dtmf : '.');
147 Call call = mCallsManager.getForegroundCall();
148 if (call != null) {
149 // TODO: Consider making this a queue instead of starting/stopping
150 // in quick succession.
151 mCallsManager.playDtmfTone(call, (char) dtmf);
152 mCallsManager.stopDtmfTone(call);
153 return true;
154 }
155 return false;
156 } finally {
157 Binder.restoreCallingIdentity(token);
Brad Ebinger3165d502015-12-15 17:22:29 -0800158 Log.endSession();
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700159 }
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700160 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700161 }
162
163 @Override
164 public String getNetworkOperator() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700165 synchronized (mLock) {
166 enforceModifyPermission();
Brad Ebinger3165d502015-12-15 17:22:29 -0800167 Log.startSession("BPSI.gNO");
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700168 long token = Binder.clearCallingIdentity();
169 try {
170 Log.i(TAG, "getNetworkOperator");
171 PhoneAccount account = getBestPhoneAccount();
Santos Cordon22403a72016-04-12 16:51:55 -0700172 if (account != null && account.getLabel() != null) {
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700173 return account.getLabel().toString();
174 } else {
175 // Finally, just get the network name from telephony.
176 return TelephonyManager.from(mContext)
177 .getNetworkOperatorName();
178 }
179 } finally {
180 Binder.restoreCallingIdentity(token);
Brad Ebinger3165d502015-12-15 17:22:29 -0800181 Log.endSession();
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700182 }
183 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700184 }
185
186 @Override
187 public String getSubscriberNumber() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700188 synchronized (mLock) {
189 enforceModifyPermission();
Brad Ebinger3165d502015-12-15 17:22:29 -0800190 Log.startSession("BPSI.gSN");
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700191 long token = Binder.clearCallingIdentity();
192 try {
193 Log.i(TAG, "getSubscriberNumber");
194 String address = null;
195 PhoneAccount account = getBestPhoneAccount();
196 if (account != null) {
197 Uri addressUri = account.getAddress();
198 if (addressUri != null) {
199 address = addressUri.getSchemeSpecificPart();
200 }
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700201 }
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700202 if (TextUtils.isEmpty(address)) {
203 address = TelephonyManager.from(mContext).getLine1Number();
Andre Eisenbach31092622015-06-04 18:56:42 -0700204 if (address == null) address = "";
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700205 }
206 return address;
207 } finally {
208 Binder.restoreCallingIdentity(token);
Brad Ebinger3165d502015-12-15 17:22:29 -0800209 Log.endSession();
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700210 }
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700211 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700212 }
213
214 @Override
215 public boolean listCurrentCalls() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700216 synchronized (mLock) {
217 enforceModifyPermission();
Brad Ebinger3165d502015-12-15 17:22:29 -0800218 Log.startSession("BPSI.lCC");
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700219 long token = Binder.clearCallingIdentity();
220 try {
221 // only log if it is after we recently updated the headset state or else it can
222 // clog the android log since this can be queried every second.
223 boolean logQuery = mHeadsetUpdatedRecently;
224 mHeadsetUpdatedRecently = false;
225
226 if (logQuery) {
227 Log.i(TAG, "listcurrentCalls");
228 }
229
230 sendListOfCalls(logQuery);
231 return true;
232 } finally {
233 Binder.restoreCallingIdentity(token);
Brad Ebinger3165d502015-12-15 17:22:29 -0800234 Log.endSession();
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700235 }
Santos Cordon88a4a602014-09-29 19:32:21 -0700236 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700237 }
238
239 @Override
240 public boolean queryPhoneState() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700241 synchronized (mLock) {
242 enforceModifyPermission();
Brad Ebinger3165d502015-12-15 17:22:29 -0800243 Log.startSession("BPSI.qPS");
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700244 long token = Binder.clearCallingIdentity();
245 try {
246 Log.i(TAG, "queryPhoneState");
247 updateHeadsetWithCallState(true /* force */);
248 return true;
249 } finally {
250 Binder.restoreCallingIdentity(token);
Brad Ebinger3165d502015-12-15 17:22:29 -0800251 Log.endSession();
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700252 }
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700253 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700254 }
255
256 @Override
257 public boolean processChld(int chld) throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700258 synchronized (mLock) {
259 enforceModifyPermission();
Brad Ebinger3165d502015-12-15 17:22:29 -0800260 Log.startSession("BPSI.pC");
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700261 long token = Binder.clearCallingIdentity();
262 try {
263 Log.i(TAG, "processChld %d", chld);
264 return BluetoothPhoneServiceImpl.this.processChld(chld);
265 } finally {
266 Binder.restoreCallingIdentity(token);
Brad Ebinger3165d502015-12-15 17:22:29 -0800267 Log.endSession();
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700268 }
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700269 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700270 }
271
272 @Override
273 public void updateBtHandsfreeAfterRadioTechnologyChange() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700274 Log.d(TAG, "RAT change - deprecated");
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700275 // deprecated
276 }
277
278 @Override
279 public void cdmaSetSecondCallState(boolean state) throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700280 Log.d(TAG, "cdma 1 - deprecated");
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700281 // deprecated
282 }
283
284 @Override
285 public void cdmaSwapSecondCallState() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700286 Log.d(TAG, "cdma 2 - deprecated");
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700287 // deprecated
288 }
289 };
290
291 /**
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700292 * Listens to call changes from the CallsManager and calls into methods to update the bluetooth
293 * headset with the new states.
294 */
Brad Ebinger53855132015-10-30 10:58:19 -0700295 @VisibleForTesting
296 public CallsManagerListener mCallsManagerListener = new CallsManagerListenerBase() {
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700297 @Override
298 public void onCallAdded(Call call) {
Tyler Gunnf15dc332016-06-07 16:01:41 -0700299 if (call.isExternalCall()) {
300 return;
301 }
Tyler Gunncd685e52014-10-10 11:48:25 -0700302 updateHeadsetWithCallState(false /* force */);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700303 }
304
305 @Override
306 public void onCallRemoved(Call call) {
Tyler Gunnf15dc332016-06-07 16:01:41 -0700307 if (call.isExternalCall()) {
308 return;
309 }
Santos Cordon33fff492014-09-25 14:57:01 -0700310 mClccIndexMap.remove(call);
Tyler Gunncd685e52014-10-10 11:48:25 -0700311 updateHeadsetWithCallState(false /* force */);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700312 }
313
Tyler Gunn1a40c4f2016-04-14 14:29:45 -0700314 /**
315 * Where a call which was external becomes a regular call, or a regular call becomes
316 * external, treat as an add or remove, respectively.
317 *
318 * @param call The call.
319 * @param isExternalCall {@code True} if the call became external, {@code false} otherwise.
320 */
321 @Override
322 public void onExternalCallChanged(Call call, boolean isExternalCall) {
323 if (isExternalCall) {
324 onCallRemoved(call);
325 } else {
326 onCallAdded(call);
327 }
328 }
329
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700330 @Override
331 public void onCallStateChanged(Call call, int oldState, int newState) {
Tyler Gunnf15dc332016-06-07 16:01:41 -0700332 if (call.isExternalCall()) {
333 return;
334 }
Yorke Lee720bcbe2014-10-22 18:09:15 -0700335 // If a call is being put on hold because of a new connecting call, ignore the
336 // CONNECTING since the BT state update needs to send out the numHeld = 1 + dialing
337 // state atomically.
338 // When the call later transitions to DIALING/DISCONNECTED we will then send out the
339 // aggregated update.
340 if (oldState == CallState.ACTIVE && newState == CallState.ON_HOLD) {
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800341 for (Call otherCall : mCallsManager.getCalls()) {
Yorke Lee720bcbe2014-10-22 18:09:15 -0700342 if (otherCall.getState() == CallState.CONNECTING) {
343 return;
344 }
345 }
346 }
347
348 // To have an active call and another dialing at the same time is an invalid BT
349 // state. We can assume that the active call will be automatically held which will
350 // send another update at which point we will be in the right state.
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800351 if (mCallsManager.getActiveCall() != null
Tyler Gunn1e37be52016-07-11 08:54:23 -0700352 && oldState == CallState.CONNECTING &&
353 (newState == CallState.DIALING || newState == CallState.PULLING)) {
Yorke Lee720bcbe2014-10-22 18:09:15 -0700354 return;
355 }
Tyler Gunncd685e52014-10-10 11:48:25 -0700356 updateHeadsetWithCallState(false /* force */);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700357 }
358
359 @Override
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700360 public void onIsConferencedChanged(Call call) {
Tyler Gunnf15dc332016-06-07 16:01:41 -0700361 if (call.isExternalCall()) {
362 return;
363 }
Yorke Lee720bcbe2014-10-22 18:09:15 -0700364 /*
365 * Filter certain onIsConferencedChanged callbacks. Unfortunately this needs to be done
366 * because conference change events are not atomic and multiple callbacks get fired
367 * when two calls are conferenced together. This confuses updateHeadsetWithCallState
368 * if it runs in the middle of two calls being conferenced and can cause spurious and
369 * incorrect headset state updates. One of the scenarios is described below for CDMA
370 * conference calls.
371 *
372 * 1) Call 1 and Call 2 are being merged into conference Call 3.
373 * 2) Call 1 has its parent set to Call 3, but Call 2 does not have a parent yet.
374 * 3) updateHeadsetWithCallState now thinks that there are two active calls (Call 2 and
375 * Call 3) when there is actually only one active call (Call 3).
376 */
377 if (call.getParentCall() != null) {
378 // If this call is newly conferenced, ignore the callback. We only care about the
379 // one sent for the parent conference call.
380 Log.d(this, "Ignoring onIsConferenceChanged from child call with new parent");
381 return;
382 }
383 if (call.getChildCalls().size() == 1) {
384 // If this is a parent call with only one child, ignore the callback as well since
385 // the minimum number of child calls to start a conference call is 2. We expect
386 // this to be called again when the parent call has another child call added.
387 Log.d(this, "Ignoring onIsConferenceChanged from parent with only one child call");
388 return;
389 }
Tyler Gunncd685e52014-10-10 11:48:25 -0700390 updateHeadsetWithCallState(false /* force */);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700391 }
Honggangb3ccbb32016-05-31 14:46:22 +0800392
393 @Override
394 public void onDisconnectedTonePlaying(boolean isTonePlaying) {
395 mIsDisconnectedTonePlaying = isTonePlaying;
396 updateHeadsetWithCallState(false /* force */);
397 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700398 };
399
400 /**
401 * Listens to connections and disconnections of bluetooth headsets. We need to save the current
402 * bluetooth headset so that we know where to send call updates.
403 */
Brad Ebinger53855132015-10-30 10:58:19 -0700404 @VisibleForTesting
405 public BluetoothProfile.ServiceListener mProfileListener =
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700406 new BluetoothProfile.ServiceListener() {
Brad Ebinger53855132015-10-30 10:58:19 -0700407 @Override
408 public void onServiceConnected(int profile, BluetoothProfile proxy) {
409 synchronized (mLock) {
410 setBluetoothHeadset(new BluetoothHeadsetProxy((BluetoothHeadset) proxy));
411 }
412 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700413
Brad Ebinger53855132015-10-30 10:58:19 -0700414 @Override
415 public void onServiceDisconnected(int profile) {
416 synchronized (mLock) {
417 mBluetoothHeadset = null;
418 }
419 }
420 };
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700421
422 /**
423 * Receives events for global state changes of the bluetooth adapter.
424 */
Brad Ebinger71286022015-12-09 17:13:27 -0800425 @VisibleForTesting
426 public final BroadcastReceiver mBluetoothAdapterReceiver = new BroadcastReceiver() {
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700427 @Override
428 public void onReceive(Context context, Intent intent) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700429 synchronized (mLock) {
430 int state = intent
431 .getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
432 Log.d(TAG, "Bluetooth Adapter state: %d", state);
433 if (state == BluetoothAdapter.STATE_ON) {
434 try {
435 mBinder.queryPhoneState();
436 } catch (RemoteException e) {
437 // Remote exception not expected
438 }
439 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700440 }
441 }
442 };
443
Hall Liu7948f5b2016-03-15 17:39:28 -0700444 private BluetoothAdapterProxy mBluetoothAdapter;
Brad Ebinger53855132015-10-30 10:58:19 -0700445 private BluetoothHeadsetProxy mBluetoothHeadset;
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700446
Santos Cordon33fff492014-09-25 14:57:01 -0700447 // A map from Calls to indexes used to identify calls for CLCC (C* List Current Calls).
448 private Map<Call, Integer> mClccIndexMap = new HashMap<>();
449
Santos Cordon88a4a602014-09-29 19:32:21 -0700450 private boolean mHeadsetUpdatedRecently = false;
451
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700452 private final Context mContext;
453 private final TelecomSystem.SyncRoot mLock;
454 private final CallsManager mCallsManager;
455 private final PhoneAccountRegistrar mPhoneAccountRegistrar;
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700456
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800457 public IBinder getBinder() {
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700458 return mBinder;
459 }
460
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800461 public BluetoothPhoneServiceImpl(
462 Context context,
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700463 TelecomSystem.SyncRoot lock,
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800464 CallsManager callsManager,
Hall Liu7948f5b2016-03-15 17:39:28 -0700465 BluetoothAdapterProxy bluetoothAdapter,
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800466 PhoneAccountRegistrar phoneAccountRegistrar) {
467 Log.d(this, "onCreate");
468
469 mContext = context;
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700470 mLock = lock;
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800471 mCallsManager = callsManager;
472 mPhoneAccountRegistrar = phoneAccountRegistrar;
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700473
Hall Liu7948f5b2016-03-15 17:39:28 -0700474 mBluetoothAdapter = bluetoothAdapter;
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700475 if (mBluetoothAdapter == null) {
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800476 Log.d(this, "BluetoothPhoneService shutting down, no BT Adapter found.");
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700477 return;
478 }
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800479 mBluetoothAdapter.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700480
481 IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800482 context.registerReceiver(mBluetoothAdapterReceiver, intentFilter);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700483
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800484 mCallsManager.addListener(mCallsManagerListener);
Tyler Gunncd685e52014-10-10 11:48:25 -0700485 updateHeadsetWithCallState(false /* force */);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700486 }
487
Brad Ebinger53855132015-10-30 10:58:19 -0700488 @VisibleForTesting
489 public void setBluetoothHeadset(BluetoothHeadsetProxy bluetoothHeadset) {
490 mBluetoothHeadset = bluetoothHeadset;
491 }
492
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700493 private boolean processChld(int chld) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700494 Call activeCall = mCallsManager.getActiveCall();
495 Call ringingCall = mCallsManager.getRingingCall();
496 Call heldCall = mCallsManager.getHeldCall();
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700497
Santos Cordon66fe8822014-10-10 16:10:58 -0700498 // TODO: Keeping as Log.i for now. Move to Log.d after L release if BT proves stable.
499 Log.i(TAG, "Active: %s\nRinging: %s\nHeld: %s", activeCall, ringingCall, heldCall);
Santos Cordon88a4a602014-09-29 19:32:21 -0700500
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700501 if (chld == CHLD_TYPE_RELEASEHELD) {
502 if (ringingCall != null) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700503 mCallsManager.rejectCall(ringingCall, false, null);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700504 return true;
505 } else if (heldCall != null) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700506 mCallsManager.disconnectCall(heldCall);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700507 return true;
508 }
509 } else if (chld == CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD) {
Satish Kodishalaf5bdfb52015-12-30 14:17:28 +0530510 if (activeCall == null && ringingCall == null && heldCall == null)
511 return false;
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700512 if (activeCall != null) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700513 mCallsManager.disconnectCall(activeCall);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700514 if (ringingCall != null) {
Hall Liu3cab92a2016-07-19 14:00:05 -0700515 mCallsManager.answerCall(ringingCall, VideoProfile.STATE_AUDIO_ONLY);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700516 } else if (heldCall != null) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700517 mCallsManager.unholdCall(heldCall);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700518 }
519 return true;
520 }
Satish Kodishalaf5bdfb52015-12-30 14:17:28 +0530521 if (ringingCall != null) {
522 mCallsManager.answerCall(ringingCall, ringingCall.getVideoState());
523 } else if (heldCall != null) {
524 mCallsManager.unholdCall(heldCall);
525 }
526 return true;
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700527 } else if (chld == CHLD_TYPE_HOLDACTIVE_ACCEPTHELD) {
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800528 if (activeCall != null && activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
Santos Cordon88a4a602014-09-29 19:32:21 -0700529 activeCall.swapConference();
Mallikarjuna GB0ae2df82015-06-04 18:20:21 +0530530 Log.i(TAG, "CDMA calls in conference swapped, updating headset");
531 updateHeadsetWithCallState(true /* force */);
Santos Cordon88a4a602014-09-29 19:32:21 -0700532 return true;
533 } else if (ringingCall != null) {
Hall Liu3cab92a2016-07-19 14:00:05 -0700534 mCallsManager.answerCall(ringingCall, VideoProfile.STATE_AUDIO_ONLY);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700535 return true;
536 } else if (heldCall != null) {
537 // CallsManager will hold any active calls when unhold() is called on a
538 // currently-held call.
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700539 mCallsManager.unholdCall(heldCall);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700540 return true;
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800541 } else if (activeCall != null && activeCall.can(Connection.CAPABILITY_HOLD)) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700542 mCallsManager.holdCall(activeCall);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700543 return true;
544 }
545 } else if (chld == CHLD_TYPE_ADDHELDTOCONF) {
546 if (activeCall != null) {
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800547 if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
Santos Cordon88a4a602014-09-29 19:32:21 -0700548 activeCall.mergeConference();
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700549 return true;
Santos Cordon88a4a602014-09-29 19:32:21 -0700550 } else {
551 List<Call> conferenceable = activeCall.getConferenceableCalls();
552 if (!conferenceable.isEmpty()) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700553 mCallsManager.conference(activeCall, conferenceable.get(0));
Santos Cordon88a4a602014-09-29 19:32:21 -0700554 return true;
Brad Ebinger53855132015-10-30 10:58:19 -0700555 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700556 }
557 }
558 }
559 return false;
560 }
561
562 private void enforceModifyPermission() {
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800563 mContext.enforceCallingOrSelfPermission(
564 android.Manifest.permission.MODIFY_PHONE_STATE, null);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700565 }
566
Santos Cordon88a4a602014-09-29 19:32:21 -0700567 private void sendListOfCalls(boolean shouldLog) {
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800568 Collection<Call> mCalls = mCallsManager.getCalls();
Santos Cordon33fff492014-09-25 14:57:01 -0700569 for (Call call : mCalls) {
570 // We don't send the parent conference call to the bluetooth device.
Tyler Gunn9365c272015-06-29 09:18:31 -0700571 // We do, however want to send conferences that have no children to the bluetooth
572 // device (e.g. IMS Conference).
573 if (!call.isConference() ||
574 (call.isConference() && call
575 .can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN))) {
Santos Cordon88a4a602014-09-29 19:32:21 -0700576 sendClccForCall(call, shouldLog);
Santos Cordon33fff492014-09-25 14:57:01 -0700577 }
578 }
579 sendClccEndMarker();
580 }
581
582 /**
583 * Sends a single clcc (C* List Current Calls) event for the specified call.
584 */
Santos Cordon88a4a602014-09-29 19:32:21 -0700585 private void sendClccForCall(Call call, boolean shouldLog) {
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800586 boolean isForeground = mCallsManager.getForegroundCall() == call;
Santos Cordon33fff492014-09-25 14:57:01 -0700587 int state = convertCallState(call.getState(), isForeground);
Santos Cordon88a4a602014-09-29 19:32:21 -0700588 boolean isPartOfConference = false;
Tyler Gunn9365c272015-06-29 09:18:31 -0700589 boolean isConferenceWithNoChildren = call.isConference() && call
590 .can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
Nancy Chen05a9e402014-09-26 14:14:32 -0700591
592 if (state == CALL_STATE_IDLE) {
593 return;
594 }
595
Santos Cordon88a4a602014-09-29 19:32:21 -0700596 Call conferenceCall = call.getParentCall();
597 if (conferenceCall != null) {
598 isPartOfConference = true;
599
600 // Run some alternative states for Conference-level merge/swap support.
601 // Basically, if call supports swapping or merging at the conference-level, then we need
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800602 // to expose the calls as having distinct states (ACTIVE vs CAPABILITY_HOLD) or the
603 // functionality won't show up on the bluetooth device.
Santos Cordon88a4a602014-09-29 19:32:21 -0700604
605 // Before doing any special logic, ensure that we are dealing with an ACTIVE call and
606 // that the conference itself has a notion of the current "active" child call.
607 Call activeChild = conferenceCall.getConferenceLevelActiveCall();
608 if (state == CALL_STATE_ACTIVE && activeChild != null) {
609 // Reevaluate state if we can MERGE or if we can SWAP without previously having
610 // MERGED.
611 boolean shouldReevaluateState =
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800612 conferenceCall.can(Connection.CAPABILITY_MERGE_CONFERENCE) ||
613 (conferenceCall.can(Connection.CAPABILITY_SWAP_CONFERENCE) &&
Brad Ebinger53855132015-10-30 10:58:19 -0700614 !conferenceCall.wasConferencePreviouslyMerged());
Santos Cordon88a4a602014-09-29 19:32:21 -0700615
616 if (shouldReevaluateState) {
617 isPartOfConference = false;
618 if (call == activeChild) {
619 state = CALL_STATE_ACTIVE;
620 } else {
621 // At this point we know there is an "active" child and we know that it is
622 // not this call, so set it to HELD instead.
623 state = CALL_STATE_HELD;
624 }
625 }
626 }
Hall Liu6fccadb2017-03-07 18:01:10 -0800627 if (conferenceCall.getState() == CallState.ON_HOLD &&
628 conferenceCall.can(Connection.CAPABILITY_MANAGE_CONFERENCE)) {
629 // If the parent IMS CEP conference call is on hold, we should mark this call as
630 // being on hold regardless of what the other children are doing.
631 state = CALL_STATE_HELD;
632 }
Tyler Gunn9365c272015-06-29 09:18:31 -0700633 } else if (isConferenceWithNoChildren) {
634 // Handle the special case of an IMS conference call without conference event package
635 // support. The call will be marked as a conference, but the conference will not have
636 // child calls where conference event packages are not used by the carrier.
637 isPartOfConference = true;
Santos Cordon88a4a602014-09-29 19:32:21 -0700638 }
639
Nancy Chen05a9e402014-09-26 14:14:32 -0700640 int index = getIndexForCall(call);
641 int direction = call.isIncoming() ? 1 : 0;
Yorke Leefb70e1e2014-09-29 14:22:53 -0700642 final Uri addressUri;
643 if (call.getGatewayInfo() != null) {
644 addressUri = call.getGatewayInfo().getOriginalAddress();
645 } else {
646 addressUri = call.getHandle();
647 }
Satish Kodishalaf4bd7c22016-11-18 12:01:51 +0530648
Santos Cordon33fff492014-09-25 14:57:01 -0700649 String address = addressUri == null ? null : addressUri.getSchemeSpecificPart();
Satish Kodishalaf4bd7c22016-11-18 12:01:51 +0530650 if (address != null) {
651 address = PhoneNumberUtils.stripSeparators(address);
652 }
653
Santos Cordon33fff492014-09-25 14:57:01 -0700654 int addressType = address == null ? -1 : PhoneNumberUtils.toaFromString(address);
655
Santos Cordon88a4a602014-09-29 19:32:21 -0700656 if (shouldLog) {
657 Log.i(this, "sending clcc for call %d, %d, %d, %b, %s, %d",
658 index, direction, state, isPartOfConference, Log.piiHandle(address),
659 addressType);
660 }
Yorke Leee241cd62014-10-09 13:41:19 -0700661
662 if (mBluetoothHeadset != null) {
663 mBluetoothHeadset.clccResponse(
664 index, direction, state, 0, isPartOfConference, address, addressType);
665 }
Santos Cordon33fff492014-09-25 14:57:01 -0700666 }
667
668 private void sendClccEndMarker() {
669 // End marker is recognized with an index value of 0. All other parameters are ignored.
Yorke Leee241cd62014-10-09 13:41:19 -0700670 if (mBluetoothHeadset != null) {
671 mBluetoothHeadset.clccResponse(0 /* index */, 0, 0, 0, false, null, 0);
672 }
Santos Cordon33fff492014-09-25 14:57:01 -0700673 }
674
675 /**
676 * Returns the caches index for the specified call. If no such index exists, then an index is
677 * given (smallest number starting from 1 that isn't already taken).
678 */
679 private int getIndexForCall(Call call) {
680 if (mClccIndexMap.containsKey(call)) {
681 return mClccIndexMap.get(call);
682 }
683
684 int i = 1; // Indexes for bluetooth clcc are 1-based.
685 while (mClccIndexMap.containsValue(i)) {
686 i++;
687 }
688
689 // NOTE: Indexes are removed in {@link #onCallRemoved}.
690 mClccIndexMap.put(call, i);
691 return i;
692 }
693
Tyler Gunncd685e52014-10-10 11:48:25 -0700694 /**
695 * Sends an update of the current call state to the current Headset.
696 *
697 * @param force {@code true} if the headset state should be sent regardless if no changes to the
698 * state have occurred, {@code false} if the state should only be sent if the state has
699 * changed.
700 */
701 private void updateHeadsetWithCallState(boolean force) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700702 Call activeCall = mCallsManager.getActiveCall();
703 Call ringingCall = mCallsManager.getRingingCall();
704 Call heldCall = mCallsManager.getHeldCall();
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700705
706 int bluetoothCallState = getBluetoothCallStateForUpdate();
707
708 String ringingAddress = null;
709 int ringingAddressType = 128;
Santos Cordonecaaeac2014-11-05 20:59:04 -0800710 if (ringingCall != null && ringingCall.getHandle() != null) {
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700711 ringingAddress = ringingCall.getHandle().getSchemeSpecificPart();
712 if (ringingAddress != null) {
713 ringingAddressType = PhoneNumberUtils.toaFromString(ringingAddress);
714 }
715 }
716 if (ringingAddress == null) {
717 ringingAddress = "";
718 }
719
Santos Cordona0b46122014-09-29 14:20:21 -0700720 int numActiveCalls = activeCall == null ? 0 : 1;
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700721 int numHeldCalls = mCallsManager.getNumHeldCalls();
Hall Liu6a89ba22017-07-31 19:04:48 -0700722 int numChildrenOfActiveCall = activeCall == null ? 0 : activeCall.getChildCalls().size();
723
Roshan Pius7d7cf272015-08-28 11:10:56 -0700724 // Intermediate state for GSM calls which are in the process of being swapped.
725 // TODO: Should we be hardcoding this value to 2 or should we check if all top level calls
726 // are held?
727 boolean callsPendingSwitch = (numHeldCalls == 2);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700728
Santos Cordon88a4a602014-09-29 19:32:21 -0700729 // For conference calls which support swapping the active call within the conference
730 // (namely CDMA calls) we need to expose that as a held call in order for the BT device
731 // to show "swap" and "merge" functionality.
Yorke Lee720bcbe2014-10-22 18:09:15 -0700732 boolean ignoreHeldCallChange = false;
Tyler Gunn9365c272015-06-29 09:18:31 -0700733 if (activeCall != null && activeCall.isConference() &&
734 !activeCall.can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN)) {
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800735 if (activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
Santos Cordon88a4a602014-09-29 19:32:21 -0700736 // Indicate that BT device should show SWAP command by indicating that there is a
737 // call on hold, but only if the conference wasn't previously merged.
738 numHeldCalls = activeCall.wasConferencePreviouslyMerged() ? 0 : 1;
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800739 } else if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
Santos Cordon88a4a602014-09-29 19:32:21 -0700740 numHeldCalls = 1; // Merge is available, so expose via numHeldCalls.
741 }
Yorke Lee720bcbe2014-10-22 18:09:15 -0700742
743 for (Call childCall : activeCall.getChildCalls()) {
744 // Held call has changed due to it being combined into a CDMA conference. Keep
745 // track of this and ignore any future update since it doesn't really count as
746 // a call change.
747 if (mOldHeldCall == childCall) {
748 ignoreHeldCallChange = true;
749 break;
750 }
751 }
Santos Cordon88a4a602014-09-29 19:32:21 -0700752 }
753
Santos Cordona0b46122014-09-29 14:20:21 -0700754 if (mBluetoothHeadset != null &&
Roshan Pius7d7cf272015-08-28 11:10:56 -0700755 (force ||
756 (!callsPendingSwitch &&
757 (numActiveCalls != mNumActiveCalls ||
Hall Liu6a89ba22017-07-31 19:04:48 -0700758 numChildrenOfActiveCall != mNumChildrenOfActiveCall ||
759 numHeldCalls != mNumHeldCalls ||
760 bluetoothCallState != mBluetoothCallState ||
761 !TextUtils.equals(ringingAddress, mRingingAddress) ||
762 ringingAddressType != mRingingAddressType ||
Brad Ebinger53855132015-10-30 10:58:19 -0700763 (heldCall != mOldHeldCall && !ignoreHeldCallChange))))) {
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700764
Santos Cordona0b46122014-09-29 14:20:21 -0700765 // If the call is transitioning into the alerting state, send DIALING first.
766 // Some devices expect to see a DIALING state prior to seeing an ALERTING state
767 // so we need to send it first.
768 boolean sendDialingFirst = mBluetoothCallState != bluetoothCallState &&
769 bluetoothCallState == CALL_STATE_ALERTING;
770
Santos Cordonc0ca76e2014-10-21 15:54:19 -0700771 mOldHeldCall = heldCall;
Santos Cordona0b46122014-09-29 14:20:21 -0700772 mNumActiveCalls = numActiveCalls;
Hall Liu6a89ba22017-07-31 19:04:48 -0700773 mNumChildrenOfActiveCall = numChildrenOfActiveCall;
Santos Cordona0b46122014-09-29 14:20:21 -0700774 mNumHeldCalls = numHeldCalls;
775 mBluetoothCallState = bluetoothCallState;
776 mRingingAddress = ringingAddress;
777 mRingingAddressType = ringingAddressType;
778
779 if (sendDialingFirst) {
Yorke Lee720bcbe2014-10-22 18:09:15 -0700780 // Log in full to make logs easier to debug.
781 Log.i(TAG, "updateHeadsetWithCallState " +
782 "numActive %s, " +
783 "numHeld %s, " +
784 "callState %s, " +
785 "ringing number %s, " +
786 "ringing type %s",
787 mNumActiveCalls,
788 mNumHeldCalls,
789 CALL_STATE_DIALING,
790 Log.pii(mRingingAddress),
791 mRingingAddressType);
Santos Cordona0b46122014-09-29 14:20:21 -0700792 mBluetoothHeadset.phoneStateChanged(
793 mNumActiveCalls,
794 mNumHeldCalls,
795 CALL_STATE_DIALING,
796 mRingingAddress,
797 mRingingAddressType);
798 }
799
800 Log.i(TAG, "updateHeadsetWithCallState " +
801 "numActive %s, " +
802 "numHeld %s, " +
803 "callState %s, " +
804 "ringing number %s, " +
805 "ringing type %s",
806 mNumActiveCalls,
807 mNumHeldCalls,
808 mBluetoothCallState,
809 Log.pii(mRingingAddress),
810 mRingingAddressType);
811
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700812 mBluetoothHeadset.phoneStateChanged(
Santos Cordona0b46122014-09-29 14:20:21 -0700813 mNumActiveCalls,
814 mNumHeldCalls,
815 mBluetoothCallState,
816 mRingingAddress,
817 mRingingAddressType);
Santos Cordon88a4a602014-09-29 19:32:21 -0700818
819 mHeadsetUpdatedRecently = true;
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700820 }
821 }
822
823 private int getBluetoothCallStateForUpdate() {
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800824 CallsManager callsManager = mCallsManager;
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700825 Call ringingCall = mCallsManager.getRingingCall();
Roshan Pius7d7cf272015-08-28 11:10:56 -0700826 Call dialingCall = mCallsManager.getOutgoingCall();
Honggangb3ccbb32016-05-31 14:46:22 +0800827 boolean hasOnlyDisconnectedCalls = mCallsManager.hasOnlyDisconnectedCalls();
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700828
829 //
830 // !! WARNING !!
831 // You will note that CALL_STATE_WAITING, CALL_STATE_HELD, and CALL_STATE_ACTIVE are not
832 // used in this version of the call state mappings. This is on purpose.
833 // phone_state_change() in btif_hf.c is not written to handle these states. Only with the
834 // listCalls*() method are WAITING and ACTIVE used.
835 // Using the unsupported states here caused problems with inconsistent state in some
836 // bluetooth devices (like not getting out of ringing state after answering a call).
837 //
838 int bluetoothCallState = CALL_STATE_IDLE;
839 if (ringingCall != null) {
840 bluetoothCallState = CALL_STATE_INCOMING;
Nancy Chen05a9e402014-09-26 14:14:32 -0700841 } else if (dialingCall != null) {
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700842 bluetoothCallState = CALL_STATE_ALERTING;
Honggangb3ccbb32016-05-31 14:46:22 +0800843 } else if (hasOnlyDisconnectedCalls || mIsDisconnectedTonePlaying) {
844 // Keep the DISCONNECTED state until the disconnect tone's playback is done
845 bluetoothCallState = CALL_STATE_DISCONNECTED;
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700846 }
847 return bluetoothCallState;
848 }
849
Santos Cordon33fff492014-09-25 14:57:01 -0700850 private int convertCallState(int callState, boolean isForegroundCall) {
851 switch (callState) {
852 case CallState.NEW:
853 case CallState.ABORTED:
854 case CallState.DISCONNECTED:
855 return CALL_STATE_IDLE;
856
857 case CallState.ACTIVE:
858 return CALL_STATE_ACTIVE;
859
Santos Cordond9f90062015-10-28 15:55:34 -0700860 case CallState.CONNECTING:
861 case CallState.SELECT_PHONE_ACCOUNT:
Santos Cordon33fff492014-09-25 14:57:01 -0700862 case CallState.DIALING:
Tyler Gunn1e37be52016-07-11 08:54:23 -0700863 case CallState.PULLING:
Santos Cordon33fff492014-09-25 14:57:01 -0700864 // Yes, this is correctly returning ALERTING.
865 // "Dialing" for BT means that we have sent information to the service provider
866 // to place the call but there is no confirmation that the call is going through.
867 // When there finally is confirmation, the ringback is played which is referred to
868 // as an "alert" tone, thus, ALERTING.
869 // TODO: We should consider using the ALERTING terms in Telecom because that
870 // seems to be more industry-standard.
871 return CALL_STATE_ALERTING;
872
873 case CallState.ON_HOLD:
874 return CALL_STATE_HELD;
875
876 case CallState.RINGING:
877 if (isForegroundCall) {
878 return CALL_STATE_INCOMING;
879 } else {
880 return CALL_STATE_WAITING;
881 }
882 }
883 return CALL_STATE_IDLE;
884 }
885
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700886 /**
887 * Returns the best phone account to use for the given state of all calls.
888 * First, tries to return the phone account for the foreground call, second the default
889 * phone account for PhoneAccount.SCHEME_TEL.
890 */
891 private PhoneAccount getBestPhoneAccount() {
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800892 if (mPhoneAccountRegistrar == null) {
Santos Cordon0b5cb4d2014-12-02 02:40:10 -0800893 return null;
894 }
895
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800896 Call call = mCallsManager.getForegroundCall();
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700897
898 PhoneAccount account = null;
899 if (call != null) {
900 // First try to get the network name of the foreground call.
Tony Mak240656f2015-12-04 11:36:22 +0000901 account = mPhoneAccountRegistrar.getPhoneAccountOfCurrentUser(
Santos Cordon6a212642015-05-08 16:35:23 -0700902 call.getTargetPhoneAccount());
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700903 }
904
905 if (account == null) {
906 // Second, Try to get the label for the default Phone Account.
Tony Mak240656f2015-12-04 11:36:22 +0000907 account = mPhoneAccountRegistrar.getPhoneAccountUnchecked(
908 mPhoneAccountRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
Brad Ebinger53855132015-10-30 10:58:19 -0700909 PhoneAccount.SCHEME_TEL));
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700910 }
911 return account;
912 }
913}