blob: ca65abf018603e6220789c22f0f03e102c56329b [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;
Santos Cordon68d1a6b2014-09-19 12:25:58 -070032import android.telecom.PhoneAccount;
33import android.telephony.PhoneNumberUtils;
34import android.telephony.TelephonyManager;
35import android.text.TextUtils;
36
37import com.android.server.telecom.CallsManager.CallsManagerListener;
38
Santos Cordon33fff492014-09-25 14:57:01 -070039import java.util.Collection;
40import java.util.HashMap;
Santos Cordon68d1a6b2014-09-19 12:25:58 -070041import java.util.List;
Santos Cordon33fff492014-09-25 14:57:01 -070042import java.util.Map;
Santos Cordon68d1a6b2014-09-19 12:25:58 -070043
44/**
45 * Bluetooth headset manager for Telecom. This class shares the call state with the bluetooth device
46 * and accepts call-related commands to perform on behalf of the BT device.
47 */
Ihab Awad78a5e6b2015-02-06 10:13:05 -080048public final class BluetoothPhoneServiceImpl {
Santos Cordon68d1a6b2014-09-19 12:25:58 -070049
50 private static final String TAG = "BluetoothPhoneService";
51
Santos Cordon68d1a6b2014-09-19 12:25:58 -070052 // match up with bthf_call_state_t of bt_hf.h
53 private static final int CALL_STATE_ACTIVE = 0;
54 private static final int CALL_STATE_HELD = 1;
55 private static final int CALL_STATE_DIALING = 2;
56 private static final int CALL_STATE_ALERTING = 3;
57 private static final int CALL_STATE_INCOMING = 4;
58 private static final int CALL_STATE_WAITING = 5;
59 private static final int CALL_STATE_IDLE = 6;
60
61 // match up with bthf_call_state_t of bt_hf.h
62 // Terminate all held or set UDUB("busy") to a waiting call
63 private static final int CHLD_TYPE_RELEASEHELD = 0;
64 // Terminate all active calls and accepts a waiting/held call
65 private static final int CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD = 1;
66 // Hold all active calls and accepts a waiting/held call
67 private static final int CHLD_TYPE_HOLDACTIVE_ACCEPTHELD = 2;
68 // Add all held calls to a conference
69 private static final int CHLD_TYPE_ADDHELDTOCONF = 3;
70
Santos Cordona0b46122014-09-29 14:20:21 -070071 private int mNumActiveCalls = 0;
72 private int mNumHeldCalls = 0;
73 private int mBluetoothCallState = CALL_STATE_IDLE;
74 private String mRingingAddress = null;
75 private int mRingingAddressType = 0;
Santos Cordonc0ca76e2014-10-21 15:54:19 -070076 private Call mOldHeldCall = null;
Santos Cordona0b46122014-09-29 14:20:21 -070077
Santos Cordon68d1a6b2014-09-19 12:25:58 -070078 /**
79 * Binder implementation of IBluetoothHeadsetPhone. Implements the command interface that the
80 * bluetooth headset code uses to control call.
81 */
82 private final IBluetoothHeadsetPhone.Stub mBinder = new IBluetoothHeadsetPhone.Stub() {
83 @Override
84 public boolean answerCall() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -070085 synchronized (mLock) {
86 enforceModifyPermission();
Santos Cordonebf2d0f2015-05-15 10:28:29 -070087
88 long token = Binder.clearCallingIdentity();
89 try {
90 Log.i(TAG, "BT - answering call");
91 Call call = mCallsManager.getRingingCall();
92 if (call != null) {
Mallikarjuna GB8729c182015-06-04 18:50:53 +053093 mCallsManager.answerCall(call, call.getVideoState());
Santos Cordonebf2d0f2015-05-15 10:28:29 -070094 return true;
95 }
96 return false;
97 } finally {
98 Binder.restoreCallingIdentity(token);
Ihab Awad8d5d9dd2015-03-12 11:11:06 -070099 }
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700100
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700101 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700102 }
103
104 @Override
105 public boolean hangupCall() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700106 synchronized (mLock) {
107 enforceModifyPermission();
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700108
109 long token = Binder.clearCallingIdentity();
110 try {
111 Log.i(TAG, "BT - hanging up call");
112 Call call = mCallsManager.getForegroundCall();
113 if (call != null) {
114 mCallsManager.disconnectCall(call);
115 return true;
116 }
117 return false;
118 } finally {
119 Binder.restoreCallingIdentity(token);
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700120 }
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700121 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700122 }
123
124 @Override
125 public boolean sendDtmf(int dtmf) throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700126 synchronized (mLock) {
127 enforceModifyPermission();
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700128
129 long token = Binder.clearCallingIdentity();
130 try {
131 Log.i(TAG, "BT - sendDtmf %c", Log.DEBUG ? dtmf : '.');
132 Call call = mCallsManager.getForegroundCall();
133 if (call != null) {
134 // TODO: Consider making this a queue instead of starting/stopping
135 // in quick succession.
136 mCallsManager.playDtmfTone(call, (char) dtmf);
137 mCallsManager.stopDtmfTone(call);
138 return true;
139 }
140 return false;
141 } finally {
142 Binder.restoreCallingIdentity(token);
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700143 }
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700144 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700145 }
146
147 @Override
148 public String getNetworkOperator() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700149 synchronized (mLock) {
150 enforceModifyPermission();
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700151
152 long token = Binder.clearCallingIdentity();
153 try {
154 Log.i(TAG, "getNetworkOperator");
155 PhoneAccount account = getBestPhoneAccount();
156 if (account != null) {
157 return account.getLabel().toString();
158 } else {
159 // Finally, just get the network name from telephony.
160 return TelephonyManager.from(mContext)
161 .getNetworkOperatorName();
162 }
163 } finally {
164 Binder.restoreCallingIdentity(token);
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700165 }
166 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700167 }
168
169 @Override
170 public String getSubscriberNumber() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700171 synchronized (mLock) {
172 enforceModifyPermission();
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700173
174 long token = Binder.clearCallingIdentity();
175 try {
176 Log.i(TAG, "getSubscriberNumber");
177 String address = null;
178 PhoneAccount account = getBestPhoneAccount();
179 if (account != null) {
180 Uri addressUri = account.getAddress();
181 if (addressUri != null) {
182 address = addressUri.getSchemeSpecificPart();
183 }
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700184 }
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700185 if (TextUtils.isEmpty(address)) {
186 address = TelephonyManager.from(mContext).getLine1Number();
Andre Eisenbach31092622015-06-04 18:56:42 -0700187 if (address == null) address = "";
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700188 }
189 return address;
190 } finally {
191 Binder.restoreCallingIdentity(token);
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700192 }
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700193 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700194 }
195
196 @Override
197 public boolean listCurrentCalls() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700198 synchronized (mLock) {
199 enforceModifyPermission();
Santos Cordon88a4a602014-09-29 19:32:21 -0700200
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700201 long token = Binder.clearCallingIdentity();
202 try {
203 // only log if it is after we recently updated the headset state or else it can
204 // clog the android log since this can be queried every second.
205 boolean logQuery = mHeadsetUpdatedRecently;
206 mHeadsetUpdatedRecently = false;
207
208 if (logQuery) {
209 Log.i(TAG, "listcurrentCalls");
210 }
211
212 sendListOfCalls(logQuery);
213 return true;
214 } finally {
215 Binder.restoreCallingIdentity(token);
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700216 }
Santos Cordon88a4a602014-09-29 19:32:21 -0700217 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700218 }
219
220 @Override
221 public boolean queryPhoneState() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700222 synchronized (mLock) {
223 enforceModifyPermission();
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700224
225 long token = Binder.clearCallingIdentity();
226 try {
227 Log.i(TAG, "queryPhoneState");
228 updateHeadsetWithCallState(true /* force */);
229 return true;
230 } finally {
231 Binder.restoreCallingIdentity(token);
232 }
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700233 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700234 }
235
236 @Override
237 public boolean processChld(int chld) throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700238 synchronized (mLock) {
239 enforceModifyPermission();
Santos Cordonebf2d0f2015-05-15 10:28:29 -0700240
241 long token = Binder.clearCallingIdentity();
242 try {
243 Log.i(TAG, "processChld %d", chld);
244 return BluetoothPhoneServiceImpl.this.processChld(chld);
245 } finally {
246 Binder.restoreCallingIdentity(token);
247 }
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700248 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700249 }
250
251 @Override
252 public void updateBtHandsfreeAfterRadioTechnologyChange() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700253 Log.d(TAG, "RAT change - deprecated");
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700254 // deprecated
255 }
256
257 @Override
258 public void cdmaSetSecondCallState(boolean state) throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700259 Log.d(TAG, "cdma 1 - deprecated");
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700260 // deprecated
261 }
262
263 @Override
264 public void cdmaSwapSecondCallState() throws RemoteException {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700265 Log.d(TAG, "cdma 2 - deprecated");
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700266 // deprecated
267 }
268 };
269
270 /**
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700271 * Listens to call changes from the CallsManager and calls into methods to update the bluetooth
272 * headset with the new states.
273 */
274 private CallsManagerListener mCallsManagerListener = new CallsManagerListenerBase() {
275 @Override
276 public void onCallAdded(Call call) {
Tyler Gunncd685e52014-10-10 11:48:25 -0700277 updateHeadsetWithCallState(false /* force */);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700278 }
279
280 @Override
281 public void onCallRemoved(Call call) {
Santos Cordon33fff492014-09-25 14:57:01 -0700282 mClccIndexMap.remove(call);
Tyler Gunncd685e52014-10-10 11:48:25 -0700283 updateHeadsetWithCallState(false /* force */);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700284 }
285
286 @Override
287 public void onCallStateChanged(Call call, int oldState, int newState) {
Yorke Lee720bcbe2014-10-22 18:09:15 -0700288 // If a call is being put on hold because of a new connecting call, ignore the
289 // CONNECTING since the BT state update needs to send out the numHeld = 1 + dialing
290 // state atomically.
291 // When the call later transitions to DIALING/DISCONNECTED we will then send out the
292 // aggregated update.
293 if (oldState == CallState.ACTIVE && newState == CallState.ON_HOLD) {
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800294 for (Call otherCall : mCallsManager.getCalls()) {
Yorke Lee720bcbe2014-10-22 18:09:15 -0700295 if (otherCall.getState() == CallState.CONNECTING) {
296 return;
297 }
298 }
299 }
300
301 // To have an active call and another dialing at the same time is an invalid BT
302 // state. We can assume that the active call will be automatically held which will
303 // send another update at which point we will be in the right state.
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800304 if (mCallsManager.getActiveCall() != null
Yorke Lee720bcbe2014-10-22 18:09:15 -0700305 && oldState == CallState.CONNECTING && newState == CallState.DIALING) {
306 return;
307 }
Tyler Gunncd685e52014-10-10 11:48:25 -0700308 updateHeadsetWithCallState(false /* force */);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700309 }
310
311 @Override
312 public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
Yorke Lee720bcbe2014-10-22 18:09:15 -0700313 // The BluetoothPhoneService does not need to respond to changes in foreground calls,
314 // which are always accompanied by call state changes anyway.
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700315 }
316
317 @Override
318 public void onIsConferencedChanged(Call call) {
Yorke Lee720bcbe2014-10-22 18:09:15 -0700319 /*
320 * Filter certain onIsConferencedChanged callbacks. Unfortunately this needs to be done
321 * because conference change events are not atomic and multiple callbacks get fired
322 * when two calls are conferenced together. This confuses updateHeadsetWithCallState
323 * if it runs in the middle of two calls being conferenced and can cause spurious and
324 * incorrect headset state updates. One of the scenarios is described below for CDMA
325 * conference calls.
326 *
327 * 1) Call 1 and Call 2 are being merged into conference Call 3.
328 * 2) Call 1 has its parent set to Call 3, but Call 2 does not have a parent yet.
329 * 3) updateHeadsetWithCallState now thinks that there are two active calls (Call 2 and
330 * Call 3) when there is actually only one active call (Call 3).
331 */
332 if (call.getParentCall() != null) {
333 // If this call is newly conferenced, ignore the callback. We only care about the
334 // one sent for the parent conference call.
335 Log.d(this, "Ignoring onIsConferenceChanged from child call with new parent");
336 return;
337 }
338 if (call.getChildCalls().size() == 1) {
339 // If this is a parent call with only one child, ignore the callback as well since
340 // the minimum number of child calls to start a conference call is 2. We expect
341 // this to be called again when the parent call has another child call added.
342 Log.d(this, "Ignoring onIsConferenceChanged from parent with only one child call");
343 return;
344 }
Tyler Gunncd685e52014-10-10 11:48:25 -0700345 updateHeadsetWithCallState(false /* force */);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700346 }
347 };
348
349 /**
350 * Listens to connections and disconnections of bluetooth headsets. We need to save the current
351 * bluetooth headset so that we know where to send call updates.
352 */
353 private BluetoothProfile.ServiceListener mProfileListener =
354 new BluetoothProfile.ServiceListener() {
355 @Override
356 public void onServiceConnected(int profile, BluetoothProfile proxy) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700357 synchronized (mLock) {
358 mBluetoothHeadset = (BluetoothHeadset) proxy;
359 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700360 }
361
362 @Override
363 public void onServiceDisconnected(int profile) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700364 synchronized (mLock) {
365 mBluetoothHeadset = null;
366 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700367 }
368 };
369
370 /**
371 * Receives events for global state changes of the bluetooth adapter.
372 */
373 private final BroadcastReceiver mBluetoothAdapterReceiver = new BroadcastReceiver() {
374 @Override
375 public void onReceive(Context context, Intent intent) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700376 synchronized (mLock) {
377 int state = intent
378 .getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
379 Log.d(TAG, "Bluetooth Adapter state: %d", state);
380 if (state == BluetoothAdapter.STATE_ON) {
381 try {
382 mBinder.queryPhoneState();
383 } catch (RemoteException e) {
384 // Remote exception not expected
385 }
386 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700387 }
388 }
389 };
390
391 private BluetoothAdapter mBluetoothAdapter;
392 private BluetoothHeadset mBluetoothHeadset;
393
Santos Cordon33fff492014-09-25 14:57:01 -0700394 // A map from Calls to indexes used to identify calls for CLCC (C* List Current Calls).
395 private Map<Call, Integer> mClccIndexMap = new HashMap<>();
396
Santos Cordon88a4a602014-09-29 19:32:21 -0700397 private boolean mHeadsetUpdatedRecently = false;
398
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700399 private final Context mContext;
400 private final TelecomSystem.SyncRoot mLock;
401 private final CallsManager mCallsManager;
402 private final PhoneAccountRegistrar mPhoneAccountRegistrar;
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700403
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800404 public IBinder getBinder() {
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700405 return mBinder;
406 }
407
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800408 public BluetoothPhoneServiceImpl(
409 Context context,
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700410 TelecomSystem.SyncRoot lock,
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800411 CallsManager callsManager,
412 PhoneAccountRegistrar phoneAccountRegistrar) {
413 Log.d(this, "onCreate");
414
415 mContext = context;
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700416 mLock = lock;
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800417 mCallsManager = callsManager;
418 mPhoneAccountRegistrar = phoneAccountRegistrar;
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700419
420 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
421 if (mBluetoothAdapter == null) {
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800422 Log.d(this, "BluetoothPhoneService shutting down, no BT Adapter found.");
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700423 return;
424 }
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800425 mBluetoothAdapter.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700426
427 IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800428 context.registerReceiver(mBluetoothAdapterReceiver, intentFilter);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700429
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800430 mCallsManager.addListener(mCallsManagerListener);
Tyler Gunncd685e52014-10-10 11:48:25 -0700431 updateHeadsetWithCallState(false /* force */);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700432 }
433
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700434 private boolean processChld(int chld) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700435 Call activeCall = mCallsManager.getActiveCall();
436 Call ringingCall = mCallsManager.getRingingCall();
437 Call heldCall = mCallsManager.getHeldCall();
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700438
Santos Cordon66fe8822014-10-10 16:10:58 -0700439 // TODO: Keeping as Log.i for now. Move to Log.d after L release if BT proves stable.
440 Log.i(TAG, "Active: %s\nRinging: %s\nHeld: %s", activeCall, ringingCall, heldCall);
Santos Cordon88a4a602014-09-29 19:32:21 -0700441
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700442 if (chld == CHLD_TYPE_RELEASEHELD) {
443 if (ringingCall != null) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700444 mCallsManager.rejectCall(ringingCall, false, null);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700445 return true;
446 } else if (heldCall != null) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700447 mCallsManager.disconnectCall(heldCall);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700448 return true;
449 }
450 } else if (chld == CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD) {
Satish Kodishalaf5bdfb52015-12-30 14:17:28 +0530451 if (activeCall == null && ringingCall == null && heldCall == null)
452 return false;
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700453 if (activeCall != null) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700454 mCallsManager.disconnectCall(activeCall);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700455 }
Satish Kodishalaf5bdfb52015-12-30 14:17:28 +0530456 if (ringingCall != null) {
457 mCallsManager.answerCall(ringingCall, ringingCall.getVideoState());
458 } else if (heldCall != null) {
459 mCallsManager.unholdCall(heldCall);
460 }
461 return true;
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700462 } else if (chld == CHLD_TYPE_HOLDACTIVE_ACCEPTHELD) {
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800463 if (activeCall != null && activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
Santos Cordon88a4a602014-09-29 19:32:21 -0700464 activeCall.swapConference();
Mallikarjuna GB0ae2df82015-06-04 18:20:21 +0530465 Log.i(TAG, "CDMA calls in conference swapped, updating headset");
466 updateHeadsetWithCallState(true /* force */);
Santos Cordon88a4a602014-09-29 19:32:21 -0700467 return true;
468 } else if (ringingCall != null) {
Mallikarjuna GB8729c182015-06-04 18:50:53 +0530469 mCallsManager.answerCall(ringingCall, ringingCall.getVideoState());
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700470 return true;
471 } else if (heldCall != null) {
472 // CallsManager will hold any active calls when unhold() is called on a
473 // currently-held call.
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700474 mCallsManager.unholdCall(heldCall);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700475 return true;
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800476 } else if (activeCall != null && activeCall.can(Connection.CAPABILITY_HOLD)) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700477 mCallsManager.holdCall(activeCall);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700478 return true;
479 }
480 } else if (chld == CHLD_TYPE_ADDHELDTOCONF) {
481 if (activeCall != null) {
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800482 if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
Santos Cordon88a4a602014-09-29 19:32:21 -0700483 activeCall.mergeConference();
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700484 return true;
Santos Cordon88a4a602014-09-29 19:32:21 -0700485 } else {
486 List<Call> conferenceable = activeCall.getConferenceableCalls();
487 if (!conferenceable.isEmpty()) {
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700488 mCallsManager.conference(activeCall, conferenceable.get(0));
Santos Cordon88a4a602014-09-29 19:32:21 -0700489 return true;
490 }
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700491 }
492 }
493 }
494 return false;
495 }
496
497 private void enforceModifyPermission() {
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800498 mContext.enforceCallingOrSelfPermission(
499 android.Manifest.permission.MODIFY_PHONE_STATE, null);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700500 }
501
Santos Cordon88a4a602014-09-29 19:32:21 -0700502 private void sendListOfCalls(boolean shouldLog) {
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800503 Collection<Call> mCalls = mCallsManager.getCalls();
Santos Cordon33fff492014-09-25 14:57:01 -0700504 for (Call call : mCalls) {
505 // We don't send the parent conference call to the bluetooth device.
Tyler Gunn9365c272015-06-29 09:18:31 -0700506 // We do, however want to send conferences that have no children to the bluetooth
507 // device (e.g. IMS Conference).
508 if (!call.isConference() ||
509 (call.isConference() && call
510 .can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN))) {
Santos Cordon88a4a602014-09-29 19:32:21 -0700511 sendClccForCall(call, shouldLog);
Santos Cordon33fff492014-09-25 14:57:01 -0700512 }
513 }
514 sendClccEndMarker();
515 }
516
517 /**
518 * Sends a single clcc (C* List Current Calls) event for the specified call.
519 */
Santos Cordon88a4a602014-09-29 19:32:21 -0700520 private void sendClccForCall(Call call, boolean shouldLog) {
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800521 boolean isForeground = mCallsManager.getForegroundCall() == call;
Santos Cordon33fff492014-09-25 14:57:01 -0700522 int state = convertCallState(call.getState(), isForeground);
Santos Cordon88a4a602014-09-29 19:32:21 -0700523 boolean isPartOfConference = false;
Tyler Gunn9365c272015-06-29 09:18:31 -0700524 boolean isConferenceWithNoChildren = call.isConference() && call
525 .can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
Nancy Chen05a9e402014-09-26 14:14:32 -0700526
527 if (state == CALL_STATE_IDLE) {
528 return;
529 }
530
Santos Cordon88a4a602014-09-29 19:32:21 -0700531 Call conferenceCall = call.getParentCall();
532 if (conferenceCall != null) {
533 isPartOfConference = true;
534
535 // Run some alternative states for Conference-level merge/swap support.
536 // Basically, if call supports swapping or merging at the conference-level, then we need
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800537 // to expose the calls as having distinct states (ACTIVE vs CAPABILITY_HOLD) or the
538 // functionality won't show up on the bluetooth device.
Santos Cordon88a4a602014-09-29 19:32:21 -0700539
540 // Before doing any special logic, ensure that we are dealing with an ACTIVE call and
541 // that the conference itself has a notion of the current "active" child call.
542 Call activeChild = conferenceCall.getConferenceLevelActiveCall();
543 if (state == CALL_STATE_ACTIVE && activeChild != null) {
544 // Reevaluate state if we can MERGE or if we can SWAP without previously having
545 // MERGED.
546 boolean shouldReevaluateState =
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800547 conferenceCall.can(Connection.CAPABILITY_MERGE_CONFERENCE) ||
548 (conferenceCall.can(Connection.CAPABILITY_SWAP_CONFERENCE) &&
Santos Cordon88a4a602014-09-29 19:32:21 -0700549 !conferenceCall.wasConferencePreviouslyMerged());
550
551 if (shouldReevaluateState) {
552 isPartOfConference = false;
553 if (call == activeChild) {
554 state = CALL_STATE_ACTIVE;
555 } else {
556 // At this point we know there is an "active" child and we know that it is
557 // not this call, so set it to HELD instead.
558 state = CALL_STATE_HELD;
559 }
560 }
561 }
Tyler Gunn9365c272015-06-29 09:18:31 -0700562 } else if (isConferenceWithNoChildren) {
563 // Handle the special case of an IMS conference call without conference event package
564 // support. The call will be marked as a conference, but the conference will not have
565 // child calls where conference event packages are not used by the carrier.
566 isPartOfConference = true;
Santos Cordon88a4a602014-09-29 19:32:21 -0700567 }
568
Nancy Chen05a9e402014-09-26 14:14:32 -0700569 int index = getIndexForCall(call);
570 int direction = call.isIncoming() ? 1 : 0;
Yorke Leefb70e1e2014-09-29 14:22:53 -0700571 final Uri addressUri;
572 if (call.getGatewayInfo() != null) {
573 addressUri = call.getGatewayInfo().getOriginalAddress();
574 } else {
575 addressUri = call.getHandle();
576 }
Santos Cordon33fff492014-09-25 14:57:01 -0700577 String address = addressUri == null ? null : addressUri.getSchemeSpecificPart();
578 int addressType = address == null ? -1 : PhoneNumberUtils.toaFromString(address);
579
Santos Cordon88a4a602014-09-29 19:32:21 -0700580 if (shouldLog) {
581 Log.i(this, "sending clcc for call %d, %d, %d, %b, %s, %d",
582 index, direction, state, isPartOfConference, Log.piiHandle(address),
583 addressType);
584 }
Yorke Leee241cd62014-10-09 13:41:19 -0700585
586 if (mBluetoothHeadset != null) {
587 mBluetoothHeadset.clccResponse(
588 index, direction, state, 0, isPartOfConference, address, addressType);
589 }
Santos Cordon33fff492014-09-25 14:57:01 -0700590 }
591
592 private void sendClccEndMarker() {
593 // End marker is recognized with an index value of 0. All other parameters are ignored.
Yorke Leee241cd62014-10-09 13:41:19 -0700594 if (mBluetoothHeadset != null) {
595 mBluetoothHeadset.clccResponse(0 /* index */, 0, 0, 0, false, null, 0);
596 }
Santos Cordon33fff492014-09-25 14:57:01 -0700597 }
598
599 /**
600 * Returns the caches index for the specified call. If no such index exists, then an index is
601 * given (smallest number starting from 1 that isn't already taken).
602 */
603 private int getIndexForCall(Call call) {
604 if (mClccIndexMap.containsKey(call)) {
605 return mClccIndexMap.get(call);
606 }
607
608 int i = 1; // Indexes for bluetooth clcc are 1-based.
609 while (mClccIndexMap.containsValue(i)) {
610 i++;
611 }
612
613 // NOTE: Indexes are removed in {@link #onCallRemoved}.
614 mClccIndexMap.put(call, i);
615 return i;
616 }
617
Tyler Gunncd685e52014-10-10 11:48:25 -0700618 /**
619 * Sends an update of the current call state to the current Headset.
620 *
621 * @param force {@code true} if the headset state should be sent regardless if no changes to the
622 * state have occurred, {@code false} if the state should only be sent if the state has
623 * changed.
624 */
625 private void updateHeadsetWithCallState(boolean force) {
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800626 CallsManager callsManager = mCallsManager;
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700627 Call activeCall = mCallsManager.getActiveCall();
628 Call ringingCall = mCallsManager.getRingingCall();
629 Call heldCall = mCallsManager.getHeldCall();
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700630
631 int bluetoothCallState = getBluetoothCallStateForUpdate();
632
633 String ringingAddress = null;
634 int ringingAddressType = 128;
Santos Cordonecaaeac2014-11-05 20:59:04 -0800635 if (ringingCall != null && ringingCall.getHandle() != null) {
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700636 ringingAddress = ringingCall.getHandle().getSchemeSpecificPart();
637 if (ringingAddress != null) {
638 ringingAddressType = PhoneNumberUtils.toaFromString(ringingAddress);
639 }
640 }
641 if (ringingAddress == null) {
642 ringingAddress = "";
643 }
644
Santos Cordona0b46122014-09-29 14:20:21 -0700645 int numActiveCalls = activeCall == null ? 0 : 1;
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700646 int numHeldCalls = mCallsManager.getNumHeldCalls();
Roshan Pius7d7cf272015-08-28 11:10:56 -0700647 // Intermediate state for GSM calls which are in the process of being swapped.
648 // TODO: Should we be hardcoding this value to 2 or should we check if all top level calls
649 // are held?
650 boolean callsPendingSwitch = (numHeldCalls == 2);
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700651
Santos Cordon88a4a602014-09-29 19:32:21 -0700652 // For conference calls which support swapping the active call within the conference
653 // (namely CDMA calls) we need to expose that as a held call in order for the BT device
654 // to show "swap" and "merge" functionality.
Yorke Lee720bcbe2014-10-22 18:09:15 -0700655 boolean ignoreHeldCallChange = false;
Tyler Gunn9365c272015-06-29 09:18:31 -0700656 if (activeCall != null && activeCall.isConference() &&
657 !activeCall.can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN)) {
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800658 if (activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
Santos Cordon88a4a602014-09-29 19:32:21 -0700659 // Indicate that BT device should show SWAP command by indicating that there is a
660 // call on hold, but only if the conference wasn't previously merged.
661 numHeldCalls = activeCall.wasConferencePreviouslyMerged() ? 0 : 1;
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800662 } else if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
Santos Cordon88a4a602014-09-29 19:32:21 -0700663 numHeldCalls = 1; // Merge is available, so expose via numHeldCalls.
664 }
Yorke Lee720bcbe2014-10-22 18:09:15 -0700665
666 for (Call childCall : activeCall.getChildCalls()) {
667 // Held call has changed due to it being combined into a CDMA conference. Keep
668 // track of this and ignore any future update since it doesn't really count as
669 // a call change.
670 if (mOldHeldCall == childCall) {
671 ignoreHeldCallChange = true;
672 break;
673 }
674 }
Santos Cordon88a4a602014-09-29 19:32:21 -0700675 }
676
Santos Cordona0b46122014-09-29 14:20:21 -0700677 if (mBluetoothHeadset != null &&
Roshan Pius7d7cf272015-08-28 11:10:56 -0700678 (force ||
679 (!callsPendingSwitch &&
680 (numActiveCalls != mNumActiveCalls ||
681 numHeldCalls != mNumHeldCalls ||
682 bluetoothCallState != mBluetoothCallState ||
683 !TextUtils.equals(ringingAddress, mRingingAddress) ||
684 ringingAddressType != mRingingAddressType ||
685 (heldCall != mOldHeldCall && !ignoreHeldCallChange))))) {
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700686
Santos Cordona0b46122014-09-29 14:20:21 -0700687 // If the call is transitioning into the alerting state, send DIALING first.
688 // Some devices expect to see a DIALING state prior to seeing an ALERTING state
689 // so we need to send it first.
690 boolean sendDialingFirst = mBluetoothCallState != bluetoothCallState &&
691 bluetoothCallState == CALL_STATE_ALERTING;
692
Santos Cordonc0ca76e2014-10-21 15:54:19 -0700693 mOldHeldCall = heldCall;
Santos Cordona0b46122014-09-29 14:20:21 -0700694 mNumActiveCalls = numActiveCalls;
695 mNumHeldCalls = numHeldCalls;
696 mBluetoothCallState = bluetoothCallState;
697 mRingingAddress = ringingAddress;
698 mRingingAddressType = ringingAddressType;
699
700 if (sendDialingFirst) {
Yorke Lee720bcbe2014-10-22 18:09:15 -0700701 // Log in full to make logs easier to debug.
702 Log.i(TAG, "updateHeadsetWithCallState " +
703 "numActive %s, " +
704 "numHeld %s, " +
705 "callState %s, " +
706 "ringing number %s, " +
707 "ringing type %s",
708 mNumActiveCalls,
709 mNumHeldCalls,
710 CALL_STATE_DIALING,
711 Log.pii(mRingingAddress),
712 mRingingAddressType);
Santos Cordona0b46122014-09-29 14:20:21 -0700713 mBluetoothHeadset.phoneStateChanged(
714 mNumActiveCalls,
715 mNumHeldCalls,
716 CALL_STATE_DIALING,
717 mRingingAddress,
718 mRingingAddressType);
719 }
720
721 Log.i(TAG, "updateHeadsetWithCallState " +
722 "numActive %s, " +
723 "numHeld %s, " +
724 "callState %s, " +
725 "ringing number %s, " +
726 "ringing type %s",
727 mNumActiveCalls,
728 mNumHeldCalls,
729 mBluetoothCallState,
730 Log.pii(mRingingAddress),
731 mRingingAddressType);
732
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700733 mBluetoothHeadset.phoneStateChanged(
Santos Cordona0b46122014-09-29 14:20:21 -0700734 mNumActiveCalls,
735 mNumHeldCalls,
736 mBluetoothCallState,
737 mRingingAddress,
738 mRingingAddressType);
Santos Cordon88a4a602014-09-29 19:32:21 -0700739
740 mHeadsetUpdatedRecently = true;
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700741 }
742 }
743
744 private int getBluetoothCallStateForUpdate() {
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800745 CallsManager callsManager = mCallsManager;
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700746 Call ringingCall = mCallsManager.getRingingCall();
Roshan Pius7d7cf272015-08-28 11:10:56 -0700747 Call dialingCall = mCallsManager.getOutgoingCall();
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700748
749 //
750 // !! WARNING !!
751 // You will note that CALL_STATE_WAITING, CALL_STATE_HELD, and CALL_STATE_ACTIVE are not
752 // used in this version of the call state mappings. This is on purpose.
753 // phone_state_change() in btif_hf.c is not written to handle these states. Only with the
754 // listCalls*() method are WAITING and ACTIVE used.
755 // Using the unsupported states here caused problems with inconsistent state in some
756 // bluetooth devices (like not getting out of ringing state after answering a call).
757 //
758 int bluetoothCallState = CALL_STATE_IDLE;
759 if (ringingCall != null) {
760 bluetoothCallState = CALL_STATE_INCOMING;
Nancy Chen05a9e402014-09-26 14:14:32 -0700761 } else if (dialingCall != null) {
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700762 bluetoothCallState = CALL_STATE_ALERTING;
763 }
764 return bluetoothCallState;
765 }
766
Santos Cordon33fff492014-09-25 14:57:01 -0700767 private int convertCallState(int callState, boolean isForegroundCall) {
768 switch (callState) {
769 case CallState.NEW:
770 case CallState.ABORTED:
771 case CallState.DISCONNECTED:
772 return CALL_STATE_IDLE;
773
774 case CallState.ACTIVE:
775 return CALL_STATE_ACTIVE;
776
Santos Cordond9f90062015-10-28 15:55:34 -0700777 case CallState.CONNECTING:
778 case CallState.SELECT_PHONE_ACCOUNT:
Santos Cordon33fff492014-09-25 14:57:01 -0700779 case CallState.DIALING:
780 // Yes, this is correctly returning ALERTING.
781 // "Dialing" for BT means that we have sent information to the service provider
782 // to place the call but there is no confirmation that the call is going through.
783 // When there finally is confirmation, the ringback is played which is referred to
784 // as an "alert" tone, thus, ALERTING.
785 // TODO: We should consider using the ALERTING terms in Telecom because that
786 // seems to be more industry-standard.
787 return CALL_STATE_ALERTING;
788
789 case CallState.ON_HOLD:
790 return CALL_STATE_HELD;
791
792 case CallState.RINGING:
793 if (isForegroundCall) {
794 return CALL_STATE_INCOMING;
795 } else {
796 return CALL_STATE_WAITING;
797 }
798 }
799 return CALL_STATE_IDLE;
800 }
801
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700802 /**
803 * Returns the best phone account to use for the given state of all calls.
804 * First, tries to return the phone account for the foreground call, second the default
805 * phone account for PhoneAccount.SCHEME_TEL.
806 */
807 private PhoneAccount getBestPhoneAccount() {
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800808 if (mPhoneAccountRegistrar == null) {
Santos Cordon0b5cb4d2014-12-02 02:40:10 -0800809 return null;
810 }
811
Ihab Awad78a5e6b2015-02-06 10:13:05 -0800812 Call call = mCallsManager.getForegroundCall();
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700813
814 PhoneAccount account = null;
815 if (call != null) {
816 // First try to get the network name of the foreground call.
Santos Cordon6a212642015-05-08 16:35:23 -0700817 account = mPhoneAccountRegistrar.getPhoneAccountCheckCallingUser(
818 call.getTargetPhoneAccount());
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700819 }
820
821 if (account == null) {
822 // Second, Try to get the label for the default Phone Account.
Santos Cordon6a212642015-05-08 16:35:23 -0700823 account = mPhoneAccountRegistrar.getPhoneAccountCheckCallingUser(
824 mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(
825 PhoneAccount.SCHEME_TEL));
Santos Cordon68d1a6b2014-09-19 12:25:58 -0700826 }
827 return account;
828 }
829}