blob: 93d4f713180956d468aba118cdf072e6757df09e [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;
69
70 // match up with bthf_call_state_t of bt_hf.h
71 // Terminate all held or set UDUB("busy") to a waiting call
72 private static final int CHLD_TYPE_RELEASEHELD = 0;
73 // Terminate all active calls and accepts a waiting/held call
74 private static final int CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD = 1;
75 // Hold all active calls and accepts a waiting/held call
76 private static final int CHLD_TYPE_HOLDACTIVE_ACCEPTHELD = 2;
77 // Add all held calls to a conference
78 private static final int CHLD_TYPE_ADDHELDTOCONF = 3;
79
Santos Cordona0b46122014-09-29 14:20:21 -070080 private int mNumActiveCalls = 0;
81 private int mNumHeldCalls = 0;
Hall Liu6a89ba22017-07-31 19:04:48 -070082 private int mNumChildrenOfActiveCall = 0;
Santos Cordona0b46122014-09-29 14:20:21 -070083 private int mBluetoothCallState = CALL_STATE_IDLE;
84 private String mRingingAddress = null;
85 private int mRingingAddressType = 0;
Santos Cordonc0ca76e2014-10-21 15:54:19 -070086 private Call mOldHeldCall = null;
Santos Cordona0b46122014-09-29 14:20:21 -070087
Santos Cordon68d1a6b2014-09-19 12:25:58 -070088 /**
89 * Binder implementation of IBluetoothHeadsetPhone. Implements the command interface that the
90 * bluetooth headset code uses to control call.
91 */
Brad Ebinger53855132015-10-30 10:58:19 -070092 @VisibleForTesting
93 public final IBluetoothHeadsetPhone.Stub mBinder = new IBluetoothHeadsetPhone.Stub() {
Santos Cordon68d1a6b2014-09-19 12:25:58 -070094 @Override
95 public boolean answerCall() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -070096 synchronized (mLock) {
97 enforceModifyPermission();
Brad Ebinger3165d502015-12-15 17:22:29 -080098 Log.startSession("BPSI.aC");
Santos Cordonebf2d0f2015-05-15 10:28:29 -070099 long token = Binder.clearCallingIdentity();
100 try {
101 Log.i(TAG, "BT - answering call");
102 Call call = mCallsManager.getRingingCall();
103 if (call != null) {
Hall Liu3cab92a2016-07-19 14:00:05 -0700104 mCallsManager.answerCall(call, VideoProfile.STATE_AUDIO_ONLY);
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700105 return true;
106 }
107 return false;
108 } finally {
109 Binder.restoreCallingIdentity(token);
Brad Ebinger3165d502015-12-15 17:22:29 -0800110 Log.endSession();
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700111 }
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700112
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700113 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700114 }
115
116 @Override
117 public boolean hangupCall() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700118 synchronized (mLock) {
119 enforceModifyPermission();
Brad Ebinger3165d502015-12-15 17:22:29 -0800120 Log.startSession("BPSI.hC");
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700121 long token = Binder.clearCallingIdentity();
122 try {
123 Log.i(TAG, "BT - hanging up call");
124 Call call = mCallsManager.getForegroundCall();
125 if (call != null) {
126 mCallsManager.disconnectCall(call);
127 return true;
128 }
129 return false;
130 } finally {
131 Binder.restoreCallingIdentity(token);
Brad Ebinger3165d502015-12-15 17:22:29 -0800132 Log.endSession();
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700133 }
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700134 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700135 }
136
137 @Override
138 public boolean sendDtmf(int dtmf) throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700139 synchronized (mLock) {
140 enforceModifyPermission();
Brad Ebinger3165d502015-12-15 17:22:29 -0800141 Log.startSession("BPSI.sD");
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700142 long token = Binder.clearCallingIdentity();
143 try {
144 Log.i(TAG, "BT - sendDtmf %c", Log.DEBUG ? dtmf : '.');
145 Call call = mCallsManager.getForegroundCall();
146 if (call != null) {
147 // TODO: Consider making this a queue instead of starting/stopping
148 // in quick succession.
149 mCallsManager.playDtmfTone(call, (char) dtmf);
150 mCallsManager.stopDtmfTone(call);
151 return true;
152 }
153 return false;
154 } finally {
155 Binder.restoreCallingIdentity(token);
Brad Ebinger3165d502015-12-15 17:22:29 -0800156 Log.endSession();
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700157 }
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700158 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700159 }
160
161 @Override
162 public String getNetworkOperator() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700163 synchronized (mLock) {
164 enforceModifyPermission();
Brad Ebinger3165d502015-12-15 17:22:29 -0800165 Log.startSession("BPSI.gNO");
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700166 long token = Binder.clearCallingIdentity();
167 try {
168 Log.i(TAG, "getNetworkOperator");
169 PhoneAccount account = getBestPhoneAccount();
Santos Cordon22403a72016-04-12 16:51:55 -0700170 if (account != null && account.getLabel() != null) {
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700171 return account.getLabel().toString();
172 } else {
173 // Finally, just get the network name from telephony.
174 return TelephonyManager.from(mContext)
175 .getNetworkOperatorName();
176 }
177 } finally {
178 Binder.restoreCallingIdentity(token);
Brad Ebinger3165d502015-12-15 17:22:29 -0800179 Log.endSession();
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700180 }
181 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700182 }
183
184 @Override
185 public String getSubscriberNumber() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700186 synchronized (mLock) {
187 enforceModifyPermission();
Brad Ebinger3165d502015-12-15 17:22:29 -0800188 Log.startSession("BPSI.gSN");
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700189 long token = Binder.clearCallingIdentity();
190 try {
191 Log.i(TAG, "getSubscriberNumber");
192 String address = null;
193 PhoneAccount account = getBestPhoneAccount();
194 if (account != null) {
195 Uri addressUri = account.getAddress();
196 if (addressUri != null) {
197 address = addressUri.getSchemeSpecificPart();
198 }
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700199 }
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700200 if (TextUtils.isEmpty(address)) {
201 address = TelephonyManager.from(mContext).getLine1Number();
Andre Eisenbach31092622015-06-04 18:56:42 -0700202 if (address == null) address = "";
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700203 }
204 return address;
205 } finally {
206 Binder.restoreCallingIdentity(token);
Brad Ebinger3165d502015-12-15 17:22:29 -0800207 Log.endSession();
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700208 }
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700209 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700210 }
211
212 @Override
213 public boolean listCurrentCalls() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700214 synchronized (mLock) {
215 enforceModifyPermission();
Brad Ebinger3165d502015-12-15 17:22:29 -0800216 Log.startSession("BPSI.lCC");
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700217 long token = Binder.clearCallingIdentity();
218 try {
219 // only log if it is after we recently updated the headset state or else it can
220 // clog the android log since this can be queried every second.
221 boolean logQuery = mHeadsetUpdatedRecently;
222 mHeadsetUpdatedRecently = false;
223
224 if (logQuery) {
225 Log.i(TAG, "listcurrentCalls");
226 }
227
228 sendListOfCalls(logQuery);
229 return true;
230 } finally {
231 Binder.restoreCallingIdentity(token);
Brad Ebinger3165d502015-12-15 17:22:29 -0800232 Log.endSession();
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700233 }
Santos Cordon88a4a602014-09-29 19:32:21 -0700234 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700235 }
236
237 @Override
238 public boolean queryPhoneState() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700239 synchronized (mLock) {
240 enforceModifyPermission();
Brad Ebinger3165d502015-12-15 17:22:29 -0800241 Log.startSession("BPSI.qPS");
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700242 long token = Binder.clearCallingIdentity();
243 try {
244 Log.i(TAG, "queryPhoneState");
245 updateHeadsetWithCallState(true /* force */);
246 return true;
247 } finally {
248 Binder.restoreCallingIdentity(token);
Brad Ebinger3165d502015-12-15 17:22:29 -0800249 Log.endSession();
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700250 }
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700251 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700252 }
253
254 @Override
255 public boolean processChld(int chld) throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700256 synchronized (mLock) {
257 enforceModifyPermission();
Brad Ebinger3165d502015-12-15 17:22:29 -0800258 Log.startSession("BPSI.pC");
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700259 long token = Binder.clearCallingIdentity();
260 try {
261 Log.i(TAG, "processChld %d", chld);
262 return BluetoothPhoneServiceImpl.this.processChld(chld);
263 } finally {
264 Binder.restoreCallingIdentity(token);
Brad Ebinger3165d502015-12-15 17:22:29 -0800265 Log.endSession();
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700266 }
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700267 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700268 }
269
270 @Override
271 public void updateBtHandsfreeAfterRadioTechnologyChange() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700272 Log.d(TAG, "RAT change - deprecated");
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700273 // deprecated
274 }
275
276 @Override
277 public void cdmaSetSecondCallState(boolean state) throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700278 Log.d(TAG, "cdma 1 - deprecated");
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700279 // deprecated
280 }
281
282 @Override
283 public void cdmaSwapSecondCallState() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700284 Log.d(TAG, "cdma 2 - deprecated");
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700285 // deprecated
286 }
287 };
288
289 /**
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700290 * Listens to call changes from the CallsManager and calls into methods to update the bluetooth
291 * headset with the new states.
292 */
Brad Ebinger53855132015-10-30 10:58:19 -0700293 @VisibleForTesting
294 public CallsManagerListener mCallsManagerListener = new CallsManagerListenerBase() {
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700295 @Override
296 public void onCallAdded(Call call) {
Tyler Gunnf15dc332016-06-07 16:01:41 -0700297 if (call.isExternalCall()) {
298 return;
299 }
Tyler Gunncd685e52014-10-10 11:48:25 -0700300 updateHeadsetWithCallState(false /* force */);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700301 }
302
303 @Override
304 public void onCallRemoved(Call call) {
Tyler Gunnf15dc332016-06-07 16:01:41 -0700305 if (call.isExternalCall()) {
306 return;
307 }
Santos Cordon33fff492014-09-25 14:57:01 -0700308 mClccIndexMap.remove(call);
Tyler Gunncd685e52014-10-10 11:48:25 -0700309 updateHeadsetWithCallState(false /* force */);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700310 }
311
Tyler Gunn1a40c4f2016-04-14 14:29:45 -0700312 /**
313 * Where a call which was external becomes a regular call, or a regular call becomes
314 * external, treat as an add or remove, respectively.
315 *
316 * @param call The call.
317 * @param isExternalCall {@code True} if the call became external, {@code false} otherwise.
318 */
319 @Override
320 public void onExternalCallChanged(Call call, boolean isExternalCall) {
321 if (isExternalCall) {
322 onCallRemoved(call);
323 } else {
324 onCallAdded(call);
325 }
326 }
327
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700328 @Override
329 public void onCallStateChanged(Call call, int oldState, int newState) {
Tyler Gunnf15dc332016-06-07 16:01:41 -0700330 if (call.isExternalCall()) {
331 return;
332 }
Yorke Lee720bcbe2014-10-22 18:09:15 -0700333 // If a call is being put on hold because of a new connecting call, ignore the
334 // CONNECTING since the BT state update needs to send out the numHeld = 1 + dialing
335 // state atomically.
336 // When the call later transitions to DIALING/DISCONNECTED we will then send out the
337 // aggregated update.
338 if (oldState == CallState.ACTIVE && newState == CallState.ON_HOLD) {
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800339 for (Call otherCall : mCallsManager.getCalls()) {
Yorke Lee720bcbe2014-10-22 18:09:15 -0700340 if (otherCall.getState() == CallState.CONNECTING) {
341 return;
342 }
343 }
344 }
345
346 // To have an active call and another dialing at the same time is an invalid BT
347 // state. We can assume that the active call will be automatically held which will
348 // send another update at which point we will be in the right state.
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800349 if (mCallsManager.getActiveCall() != null
Tyler Gunn1e37be52016-07-11 08:54:23 -0700350 && oldState == CallState.CONNECTING &&
351 (newState == CallState.DIALING || newState == CallState.PULLING)) {
Yorke Lee720bcbe2014-10-22 18:09:15 -0700352 return;
353 }
Tyler Gunncd685e52014-10-10 11:48:25 -0700354 updateHeadsetWithCallState(false /* force */);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700355 }
356
357 @Override
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700358 public void onIsConferencedChanged(Call call) {
Tyler Gunnf15dc332016-06-07 16:01:41 -0700359 if (call.isExternalCall()) {
360 return;
361 }
Yorke Lee720bcbe2014-10-22 18:09:15 -0700362 /*
363 * Filter certain onIsConferencedChanged callbacks. Unfortunately this needs to be done
364 * because conference change events are not atomic and multiple callbacks get fired
365 * when two calls are conferenced together. This confuses updateHeadsetWithCallState
366 * if it runs in the middle of two calls being conferenced and can cause spurious and
367 * incorrect headset state updates. One of the scenarios is described below for CDMA
368 * conference calls.
369 *
370 * 1) Call 1 and Call 2 are being merged into conference Call 3.
371 * 2) Call 1 has its parent set to Call 3, but Call 2 does not have a parent yet.
372 * 3) updateHeadsetWithCallState now thinks that there are two active calls (Call 2 and
373 * Call 3) when there is actually only one active call (Call 3).
374 */
375 if (call.getParentCall() != null) {
376 // If this call is newly conferenced, ignore the callback. We only care about the
377 // one sent for the parent conference call.
378 Log.d(this, "Ignoring onIsConferenceChanged from child call with new parent");
379 return;
380 }
381 if (call.getChildCalls().size() == 1) {
382 // If this is a parent call with only one child, ignore the callback as well since
383 // the minimum number of child calls to start a conference call is 2. We expect
384 // this to be called again when the parent call has another child call added.
385 Log.d(this, "Ignoring onIsConferenceChanged from parent with only one child call");
386 return;
387 }
Tyler Gunncd685e52014-10-10 11:48:25 -0700388 updateHeadsetWithCallState(false /* force */);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700389 }
390 };
391
392 /**
393 * Listens to connections and disconnections of bluetooth headsets. We need to save the current
394 * bluetooth headset so that we know where to send call updates.
395 */
Brad Ebinger53855132015-10-30 10:58:19 -0700396 @VisibleForTesting
397 public BluetoothProfile.ServiceListener mProfileListener =
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700398 new BluetoothProfile.ServiceListener() {
Brad Ebinger53855132015-10-30 10:58:19 -0700399 @Override
400 public void onServiceConnected(int profile, BluetoothProfile proxy) {
401 synchronized (mLock) {
402 setBluetoothHeadset(new BluetoothHeadsetProxy((BluetoothHeadset) proxy));
403 }
404 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700405
Brad Ebinger53855132015-10-30 10:58:19 -0700406 @Override
407 public void onServiceDisconnected(int profile) {
408 synchronized (mLock) {
409 mBluetoothHeadset = null;
410 }
411 }
412 };
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700413
414 /**
415 * Receives events for global state changes of the bluetooth adapter.
416 */
Brad Ebinger71286022015-12-09 17:13:27 -0800417 @VisibleForTesting
418 public final BroadcastReceiver mBluetoothAdapterReceiver = new BroadcastReceiver() {
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700419 @Override
420 public void onReceive(Context context, Intent intent) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700421 synchronized (mLock) {
422 int state = intent
423 .getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
424 Log.d(TAG, "Bluetooth Adapter state: %d", state);
425 if (state == BluetoothAdapter.STATE_ON) {
426 try {
427 mBinder.queryPhoneState();
428 } catch (RemoteException e) {
429 // Remote exception not expected
430 }
431 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700432 }
433 }
434 };
435
Hall Liu7948f5b2016-03-15 17:39:28 -0700436 private BluetoothAdapterProxy mBluetoothAdapter;
Brad Ebinger53855132015-10-30 10:58:19 -0700437 private BluetoothHeadsetProxy mBluetoothHeadset;
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700438
Santos Cordon33fff492014-09-25 14:57:01 -0700439 // A map from Calls to indexes used to identify calls for CLCC (C* List Current Calls).
440 private Map<Call, Integer> mClccIndexMap = new HashMap<>();
441
Santos Cordon88a4a602014-09-29 19:32:21 -0700442 private boolean mHeadsetUpdatedRecently = false;
443
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700444 private final Context mContext;
445 private final TelecomSystem.SyncRoot mLock;
446 private final CallsManager mCallsManager;
447 private final PhoneAccountRegistrar mPhoneAccountRegistrar;
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700448
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800449 public IBinder getBinder() {
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700450 return mBinder;
451 }
452
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800453 public BluetoothPhoneServiceImpl(
454 Context context,
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700455 TelecomSystem.SyncRoot lock,
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800456 CallsManager callsManager,
Hall Liu7948f5b2016-03-15 17:39:28 -0700457 BluetoothAdapterProxy bluetoothAdapter,
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800458 PhoneAccountRegistrar phoneAccountRegistrar) {
459 Log.d(this, "onCreate");
460
461 mContext = context;
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700462 mLock = lock;
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800463 mCallsManager = callsManager;
464 mPhoneAccountRegistrar = phoneAccountRegistrar;
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700465
Hall Liu7948f5b2016-03-15 17:39:28 -0700466 mBluetoothAdapter = bluetoothAdapter;
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700467 if (mBluetoothAdapter == null) {
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800468 Log.d(this, "BluetoothPhoneService shutting down, no BT Adapter found.");
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700469 return;
470 }
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800471 mBluetoothAdapter.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700472
473 IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800474 context.registerReceiver(mBluetoothAdapterReceiver, intentFilter);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700475
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800476 mCallsManager.addListener(mCallsManagerListener);
Tyler Gunncd685e52014-10-10 11:48:25 -0700477 updateHeadsetWithCallState(false /* force */);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700478 }
479
Brad Ebinger53855132015-10-30 10:58:19 -0700480 @VisibleForTesting
481 public void setBluetoothHeadset(BluetoothHeadsetProxy bluetoothHeadset) {
482 mBluetoothHeadset = bluetoothHeadset;
483 }
484
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700485 private boolean processChld(int chld) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700486 Call activeCall = mCallsManager.getActiveCall();
487 Call ringingCall = mCallsManager.getRingingCall();
488 Call heldCall = mCallsManager.getHeldCall();
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700489
Santos Cordon66fe8822014-10-10 16:10:58 -0700490 // TODO: Keeping as Log.i for now. Move to Log.d after L release if BT proves stable.
491 Log.i(TAG, "Active: %s\nRinging: %s\nHeld: %s", activeCall, ringingCall, heldCall);
Santos Cordon88a4a602014-09-29 19:32:21 -0700492
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700493 if (chld == CHLD_TYPE_RELEASEHELD) {
494 if (ringingCall != null) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700495 mCallsManager.rejectCall(ringingCall, false, null);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700496 return true;
497 } else if (heldCall != null) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700498 mCallsManager.disconnectCall(heldCall);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700499 return true;
500 }
501 } else if (chld == CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD) {
Satish Kodishalaf5bdfb52015-12-30 14:17:28 +0530502 if (activeCall == null && ringingCall == null && heldCall == null)
503 return false;
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700504 if (activeCall != null) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700505 mCallsManager.disconnectCall(activeCall);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700506 if (ringingCall != null) {
Hall Liu3cab92a2016-07-19 14:00:05 -0700507 mCallsManager.answerCall(ringingCall, VideoProfile.STATE_AUDIO_ONLY);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700508 } else if (heldCall != null) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700509 mCallsManager.unholdCall(heldCall);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700510 }
511 return true;
512 }
Satish Kodishalaf5bdfb52015-12-30 14:17:28 +0530513 if (ringingCall != null) {
514 mCallsManager.answerCall(ringingCall, ringingCall.getVideoState());
515 } else if (heldCall != null) {
516 mCallsManager.unholdCall(heldCall);
517 }
518 return true;
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700519 } else if (chld == CHLD_TYPE_HOLDACTIVE_ACCEPTHELD) {
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800520 if (activeCall != null && activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
Santos Cordon88a4a602014-09-29 19:32:21 -0700521 activeCall.swapConference();
Mallikarjuna GB0ae2df82015-06-04 18:20:21 +0530522 Log.i(TAG, "CDMA calls in conference swapped, updating headset");
523 updateHeadsetWithCallState(true /* force */);
Santos Cordon88a4a602014-09-29 19:32:21 -0700524 return true;
525 } else if (ringingCall != null) {
Hall Liu3cab92a2016-07-19 14:00:05 -0700526 mCallsManager.answerCall(ringingCall, VideoProfile.STATE_AUDIO_ONLY);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700527 return true;
528 } else if (heldCall != null) {
529 // CallsManager will hold any active calls when unhold() is called on a
530 // currently-held call.
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700531 mCallsManager.unholdCall(heldCall);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700532 return true;
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800533 } else if (activeCall != null && activeCall.can(Connection.CAPABILITY_HOLD)) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700534 mCallsManager.holdCall(activeCall);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700535 return true;
536 }
537 } else if (chld == CHLD_TYPE_ADDHELDTOCONF) {
538 if (activeCall != null) {
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800539 if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
Santos Cordon88a4a602014-09-29 19:32:21 -0700540 activeCall.mergeConference();
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700541 return true;
Santos Cordon88a4a602014-09-29 19:32:21 -0700542 } else {
543 List<Call> conferenceable = activeCall.getConferenceableCalls();
544 if (!conferenceable.isEmpty()) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700545 mCallsManager.conference(activeCall, conferenceable.get(0));
Santos Cordon88a4a602014-09-29 19:32:21 -0700546 return true;
Brad Ebinger53855132015-10-30 10:58:19 -0700547 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700548 }
549 }
550 }
551 return false;
552 }
553
554 private void enforceModifyPermission() {
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800555 mContext.enforceCallingOrSelfPermission(
556 android.Manifest.permission.MODIFY_PHONE_STATE, null);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700557 }
558
Santos Cordon88a4a602014-09-29 19:32:21 -0700559 private void sendListOfCalls(boolean shouldLog) {
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800560 Collection<Call> mCalls = mCallsManager.getCalls();
Santos Cordon33fff492014-09-25 14:57:01 -0700561 for (Call call : mCalls) {
562 // We don't send the parent conference call to the bluetooth device.
Tyler Gunn9365c272015-06-29 09:18:31 -0700563 // We do, however want to send conferences that have no children to the bluetooth
564 // device (e.g. IMS Conference).
565 if (!call.isConference() ||
566 (call.isConference() && call
567 .can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN))) {
Santos Cordon88a4a602014-09-29 19:32:21 -0700568 sendClccForCall(call, shouldLog);
Santos Cordon33fff492014-09-25 14:57:01 -0700569 }
570 }
571 sendClccEndMarker();
572 }
573
574 /**
575 * Sends a single clcc (C* List Current Calls) event for the specified call.
576 */
Santos Cordon88a4a602014-09-29 19:32:21 -0700577 private void sendClccForCall(Call call, boolean shouldLog) {
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800578 boolean isForeground = mCallsManager.getForegroundCall() == call;
Santos Cordon33fff492014-09-25 14:57:01 -0700579 int state = convertCallState(call.getState(), isForeground);
Santos Cordon88a4a602014-09-29 19:32:21 -0700580 boolean isPartOfConference = false;
Tyler Gunn9365c272015-06-29 09:18:31 -0700581 boolean isConferenceWithNoChildren = call.isConference() && call
582 .can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
Nancy Chen05a9e402014-09-26 14:14:32 -0700583
584 if (state == CALL_STATE_IDLE) {
585 return;
586 }
587
Santos Cordon88a4a602014-09-29 19:32:21 -0700588 Call conferenceCall = call.getParentCall();
589 if (conferenceCall != null) {
590 isPartOfConference = true;
591
592 // Run some alternative states for Conference-level merge/swap support.
593 // Basically, if call supports swapping or merging at the conference-level, then we need
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800594 // to expose the calls as having distinct states (ACTIVE vs CAPABILITY_HOLD) or the
595 // functionality won't show up on the bluetooth device.
Santos Cordon88a4a602014-09-29 19:32:21 -0700596
597 // Before doing any special logic, ensure that we are dealing with an ACTIVE call and
598 // that the conference itself has a notion of the current "active" child call.
599 Call activeChild = conferenceCall.getConferenceLevelActiveCall();
600 if (state == CALL_STATE_ACTIVE && activeChild != null) {
601 // Reevaluate state if we can MERGE or if we can SWAP without previously having
602 // MERGED.
603 boolean shouldReevaluateState =
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800604 conferenceCall.can(Connection.CAPABILITY_MERGE_CONFERENCE) ||
605 (conferenceCall.can(Connection.CAPABILITY_SWAP_CONFERENCE) &&
Brad Ebinger53855132015-10-30 10:58:19 -0700606 !conferenceCall.wasConferencePreviouslyMerged());
Santos Cordon88a4a602014-09-29 19:32:21 -0700607
608 if (shouldReevaluateState) {
609 isPartOfConference = false;
610 if (call == activeChild) {
611 state = CALL_STATE_ACTIVE;
612 } else {
613 // At this point we know there is an "active" child and we know that it is
614 // not this call, so set it to HELD instead.
615 state = CALL_STATE_HELD;
616 }
617 }
618 }
Hall Liu6fccadb2017-03-07 18:01:10 -0800619 if (conferenceCall.getState() == CallState.ON_HOLD &&
620 conferenceCall.can(Connection.CAPABILITY_MANAGE_CONFERENCE)) {
621 // If the parent IMS CEP conference call is on hold, we should mark this call as
622 // being on hold regardless of what the other children are doing.
623 state = CALL_STATE_HELD;
624 }
Tyler Gunn9365c272015-06-29 09:18:31 -0700625 } else if (isConferenceWithNoChildren) {
626 // Handle the special case of an IMS conference call without conference event package
627 // support. The call will be marked as a conference, but the conference will not have
628 // child calls where conference event packages are not used by the carrier.
629 isPartOfConference = true;
Santos Cordon88a4a602014-09-29 19:32:21 -0700630 }
631
Nancy Chen05a9e402014-09-26 14:14:32 -0700632 int index = getIndexForCall(call);
633 int direction = call.isIncoming() ? 1 : 0;
Yorke Leefb70e1e2014-09-29 14:22:53 -0700634 final Uri addressUri;
635 if (call.getGatewayInfo() != null) {
636 addressUri = call.getGatewayInfo().getOriginalAddress();
637 } else {
638 addressUri = call.getHandle();
639 }
Satish Kodishalaf4bd7c22016-11-18 12:01:51 +0530640
Santos Cordon33fff492014-09-25 14:57:01 -0700641 String address = addressUri == null ? null : addressUri.getSchemeSpecificPart();
Satish Kodishalaf4bd7c22016-11-18 12:01:51 +0530642 if (address != null) {
643 address = PhoneNumberUtils.stripSeparators(address);
644 }
645
Santos Cordon33fff492014-09-25 14:57:01 -0700646 int addressType = address == null ? -1 : PhoneNumberUtils.toaFromString(address);
647
Santos Cordon88a4a602014-09-29 19:32:21 -0700648 if (shouldLog) {
649 Log.i(this, "sending clcc for call %d, %d, %d, %b, %s, %d",
650 index, direction, state, isPartOfConference, Log.piiHandle(address),
651 addressType);
652 }
Yorke Leee241cd62014-10-09 13:41:19 -0700653
654 if (mBluetoothHeadset != null) {
655 mBluetoothHeadset.clccResponse(
656 index, direction, state, 0, isPartOfConference, address, addressType);
657 }
Santos Cordon33fff492014-09-25 14:57:01 -0700658 }
659
660 private void sendClccEndMarker() {
661 // End marker is recognized with an index value of 0. All other parameters are ignored.
Yorke Leee241cd62014-10-09 13:41:19 -0700662 if (mBluetoothHeadset != null) {
663 mBluetoothHeadset.clccResponse(0 /* index */, 0, 0, 0, false, null, 0);
664 }
Santos Cordon33fff492014-09-25 14:57:01 -0700665 }
666
667 /**
668 * Returns the caches index for the specified call. If no such index exists, then an index is
669 * given (smallest number starting from 1 that isn't already taken).
670 */
671 private int getIndexForCall(Call call) {
672 if (mClccIndexMap.containsKey(call)) {
673 return mClccIndexMap.get(call);
674 }
675
676 int i = 1; // Indexes for bluetooth clcc are 1-based.
677 while (mClccIndexMap.containsValue(i)) {
678 i++;
679 }
680
681 // NOTE: Indexes are removed in {@link #onCallRemoved}.
682 mClccIndexMap.put(call, i);
683 return i;
684 }
685
Tyler Gunncd685e52014-10-10 11:48:25 -0700686 /**
687 * Sends an update of the current call state to the current Headset.
688 *
689 * @param force {@code true} if the headset state should be sent regardless if no changes to the
690 * state have occurred, {@code false} if the state should only be sent if the state has
691 * changed.
692 */
693 private void updateHeadsetWithCallState(boolean force) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700694 Call activeCall = mCallsManager.getActiveCall();
695 Call ringingCall = mCallsManager.getRingingCall();
696 Call heldCall = mCallsManager.getHeldCall();
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700697
698 int bluetoothCallState = getBluetoothCallStateForUpdate();
699
700 String ringingAddress = null;
701 int ringingAddressType = 128;
Santos Cordonecaaeac2014-11-05 20:59:04 -0800702 if (ringingCall != null && ringingCall.getHandle() != null) {
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700703 ringingAddress = ringingCall.getHandle().getSchemeSpecificPart();
704 if (ringingAddress != null) {
705 ringingAddressType = PhoneNumberUtils.toaFromString(ringingAddress);
706 }
707 }
708 if (ringingAddress == null) {
709 ringingAddress = "";
710 }
711
Santos Cordona0b46122014-09-29 14:20:21 -0700712 int numActiveCalls = activeCall == null ? 0 : 1;
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700713 int numHeldCalls = mCallsManager.getNumHeldCalls();
Hall Liu6a89ba22017-07-31 19:04:48 -0700714 int numChildrenOfActiveCall = activeCall == null ? 0 : activeCall.getChildCalls().size();
715
Roshan Pius7d7cf272015-08-28 11:10:56 -0700716 // Intermediate state for GSM calls which are in the process of being swapped.
717 // TODO: Should we be hardcoding this value to 2 or should we check if all top level calls
718 // are held?
719 boolean callsPendingSwitch = (numHeldCalls == 2);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700720
Santos Cordon88a4a602014-09-29 19:32:21 -0700721 // For conference calls which support swapping the active call within the conference
722 // (namely CDMA calls) we need to expose that as a held call in order for the BT device
723 // to show "swap" and "merge" functionality.
Yorke Lee720bcbe2014-10-22 18:09:15 -0700724 boolean ignoreHeldCallChange = false;
Tyler Gunn9365c272015-06-29 09:18:31 -0700725 if (activeCall != null && activeCall.isConference() &&
726 !activeCall.can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN)) {
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800727 if (activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
Santos Cordon88a4a602014-09-29 19:32:21 -0700728 // Indicate that BT device should show SWAP command by indicating that there is a
729 // call on hold, but only if the conference wasn't previously merged.
730 numHeldCalls = activeCall.wasConferencePreviouslyMerged() ? 0 : 1;
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800731 } else if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
Santos Cordon88a4a602014-09-29 19:32:21 -0700732 numHeldCalls = 1; // Merge is available, so expose via numHeldCalls.
733 }
Yorke Lee720bcbe2014-10-22 18:09:15 -0700734
735 for (Call childCall : activeCall.getChildCalls()) {
736 // Held call has changed due to it being combined into a CDMA conference. Keep
737 // track of this and ignore any future update since it doesn't really count as
738 // a call change.
739 if (mOldHeldCall == childCall) {
740 ignoreHeldCallChange = true;
741 break;
742 }
743 }
Santos Cordon88a4a602014-09-29 19:32:21 -0700744 }
745
Santos Cordona0b46122014-09-29 14:20:21 -0700746 if (mBluetoothHeadset != null &&
Roshan Pius7d7cf272015-08-28 11:10:56 -0700747 (force ||
748 (!callsPendingSwitch &&
749 (numActiveCalls != mNumActiveCalls ||
Hall Liu6a89ba22017-07-31 19:04:48 -0700750 numChildrenOfActiveCall != mNumChildrenOfActiveCall ||
751 numHeldCalls != mNumHeldCalls ||
752 bluetoothCallState != mBluetoothCallState ||
753 !TextUtils.equals(ringingAddress, mRingingAddress) ||
754 ringingAddressType != mRingingAddressType ||
Brad Ebinger53855132015-10-30 10:58:19 -0700755 (heldCall != mOldHeldCall && !ignoreHeldCallChange))))) {
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700756
Santos Cordona0b46122014-09-29 14:20:21 -0700757 // If the call is transitioning into the alerting state, send DIALING first.
758 // Some devices expect to see a DIALING state prior to seeing an ALERTING state
759 // so we need to send it first.
760 boolean sendDialingFirst = mBluetoothCallState != bluetoothCallState &&
761 bluetoothCallState == CALL_STATE_ALERTING;
762
Santos Cordonc0ca76e2014-10-21 15:54:19 -0700763 mOldHeldCall = heldCall;
Santos Cordona0b46122014-09-29 14:20:21 -0700764 mNumActiveCalls = numActiveCalls;
Hall Liu6a89ba22017-07-31 19:04:48 -0700765 mNumChildrenOfActiveCall = numChildrenOfActiveCall;
Santos Cordona0b46122014-09-29 14:20:21 -0700766 mNumHeldCalls = numHeldCalls;
767 mBluetoothCallState = bluetoothCallState;
768 mRingingAddress = ringingAddress;
769 mRingingAddressType = ringingAddressType;
770
771 if (sendDialingFirst) {
Yorke Lee720bcbe2014-10-22 18:09:15 -0700772 // Log in full to make logs easier to debug.
773 Log.i(TAG, "updateHeadsetWithCallState " +
774 "numActive %s, " +
775 "numHeld %s, " +
776 "callState %s, " +
777 "ringing number %s, " +
778 "ringing type %s",
779 mNumActiveCalls,
780 mNumHeldCalls,
781 CALL_STATE_DIALING,
782 Log.pii(mRingingAddress),
783 mRingingAddressType);
Santos Cordona0b46122014-09-29 14:20:21 -0700784 mBluetoothHeadset.phoneStateChanged(
785 mNumActiveCalls,
786 mNumHeldCalls,
787 CALL_STATE_DIALING,
788 mRingingAddress,
789 mRingingAddressType);
790 }
791
792 Log.i(TAG, "updateHeadsetWithCallState " +
793 "numActive %s, " +
794 "numHeld %s, " +
795 "callState %s, " +
796 "ringing number %s, " +
797 "ringing type %s",
798 mNumActiveCalls,
799 mNumHeldCalls,
800 mBluetoothCallState,
801 Log.pii(mRingingAddress),
802 mRingingAddressType);
803
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700804 mBluetoothHeadset.phoneStateChanged(
Santos Cordona0b46122014-09-29 14:20:21 -0700805 mNumActiveCalls,
806 mNumHeldCalls,
807 mBluetoothCallState,
808 mRingingAddress,
809 mRingingAddressType);
Santos Cordon88a4a602014-09-29 19:32:21 -0700810
811 mHeadsetUpdatedRecently = true;
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700812 }
813 }
814
815 private int getBluetoothCallStateForUpdate() {
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800816 CallsManager callsManager = mCallsManager;
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700817 Call ringingCall = mCallsManager.getRingingCall();
Roshan Pius7d7cf272015-08-28 11:10:56 -0700818 Call dialingCall = mCallsManager.getOutgoingCall();
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700819
820 //
821 // !! WARNING !!
822 // You will note that CALL_STATE_WAITING, CALL_STATE_HELD, and CALL_STATE_ACTIVE are not
823 // used in this version of the call state mappings. This is on purpose.
824 // phone_state_change() in btif_hf.c is not written to handle these states. Only with the
825 // listCalls*() method are WAITING and ACTIVE used.
826 // Using the unsupported states here caused problems with inconsistent state in some
827 // bluetooth devices (like not getting out of ringing state after answering a call).
828 //
829 int bluetoothCallState = CALL_STATE_IDLE;
830 if (ringingCall != null) {
831 bluetoothCallState = CALL_STATE_INCOMING;
Nancy Chen05a9e402014-09-26 14:14:32 -0700832 } else if (dialingCall != null) {
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700833 bluetoothCallState = CALL_STATE_ALERTING;
834 }
835 return bluetoothCallState;
836 }
837
Santos Cordon33fff492014-09-25 14:57:01 -0700838 private int convertCallState(int callState, boolean isForegroundCall) {
839 switch (callState) {
840 case CallState.NEW:
841 case CallState.ABORTED:
842 case CallState.DISCONNECTED:
843 return CALL_STATE_IDLE;
844
845 case CallState.ACTIVE:
846 return CALL_STATE_ACTIVE;
847
Santos Cordond9f90062015-10-28 15:55:34 -0700848 case CallState.CONNECTING:
849 case CallState.SELECT_PHONE_ACCOUNT:
Santos Cordon33fff492014-09-25 14:57:01 -0700850 case CallState.DIALING:
Tyler Gunn1e37be52016-07-11 08:54:23 -0700851 case CallState.PULLING:
Santos Cordon33fff492014-09-25 14:57:01 -0700852 // Yes, this is correctly returning ALERTING.
853 // "Dialing" for BT means that we have sent information to the service provider
854 // to place the call but there is no confirmation that the call is going through.
855 // When there finally is confirmation, the ringback is played which is referred to
856 // as an "alert" tone, thus, ALERTING.
857 // TODO: We should consider using the ALERTING terms in Telecom because that
858 // seems to be more industry-standard.
859 return CALL_STATE_ALERTING;
860
861 case CallState.ON_HOLD:
862 return CALL_STATE_HELD;
863
864 case CallState.RINGING:
865 if (isForegroundCall) {
866 return CALL_STATE_INCOMING;
867 } else {
868 return CALL_STATE_WAITING;
869 }
870 }
871 return CALL_STATE_IDLE;
872 }
873
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700874 /**
875 * Returns the best phone account to use for the given state of all calls.
876 * First, tries to return the phone account for the foreground call, second the default
877 * phone account for PhoneAccount.SCHEME_TEL.
878 */
879 private PhoneAccount getBestPhoneAccount() {
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800880 if (mPhoneAccountRegistrar == null) {
Santos Cordon0b5cb4d2014-12-02 02:40:10 -0800881 return null;
882 }
883
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800884 Call call = mCallsManager.getForegroundCall();
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700885
886 PhoneAccount account = null;
887 if (call != null) {
888 // First try to get the network name of the foreground call.
Tony Mak240656f2015-12-04 11:36:22 +0000889 account = mPhoneAccountRegistrar.getPhoneAccountOfCurrentUser(
Santos Cordon6a212642015-05-08 16:35:23 -0700890 call.getTargetPhoneAccount());
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700891 }
892
893 if (account == null) {
894 // Second, Try to get the label for the default Phone Account.
Tony Mak240656f2015-12-04 11:36:22 +0000895 account = mPhoneAccountRegistrar.getPhoneAccountUnchecked(
896 mPhoneAccountRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
Brad Ebinger53855132015-10-30 10:58:19 -0700897 PhoneAccount.SCHEME_TEL));
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700898 }
899 return account;
900 }
901}