blob: ed6f37e7675419da0222a43b736e4457cb949f53 [file] [log] [blame]
Santos Cordon7d4ddf62013-07-10 11:58:08 -07001/*
2 * Copyright (C) 2012 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.phone;
18
19import android.app.Service;
20import android.bluetooth.BluetoothAdapter;
21import android.bluetooth.BluetoothDevice;
22import android.bluetooth.BluetoothHeadset;
23import android.bluetooth.BluetoothProfile;
24import android.bluetooth.IBluetoothHeadsetPhone;
25import android.content.Context;
26import android.content.Intent;
27import android.os.AsyncResult;
28import android.os.Handler;
29import android.os.IBinder;
30import android.os.Message;
31import android.os.PowerManager;
32import android.os.PowerManager.WakeLock;
33import android.os.SystemProperties;
34import android.telephony.PhoneNumberUtils;
35import android.telephony.ServiceState;
36import android.util.Log;
37
38import com.android.internal.telephony.Call;
39import com.android.internal.telephony.Connection;
40import com.android.internal.telephony.Phone;
41import com.android.internal.telephony.PhoneConstants;
42import com.android.internal.telephony.TelephonyIntents;
43import com.android.internal.telephony.CallManager;
44
45import java.io.IOException;
46import java.util.LinkedList;
47import java.util.List;
48
49/**
50 * Bluetooth headset manager for the Phone app.
51 * @hide
52 */
53public class BluetoothPhoneService extends Service {
54 private static final String TAG = "BluetoothPhoneService";
55 private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 1)
56 && (SystemProperties.getInt("ro.debuggable", 0) == 1);
57 private static final boolean VDBG = (PhoneGlobals.DBG_LEVEL >= 2); // even more logging
58
59 private static final String MODIFY_PHONE_STATE = android.Manifest.permission.MODIFY_PHONE_STATE;
60
61 private BluetoothAdapter mAdapter;
62 private CallManager mCM;
63
64 private BluetoothHeadset mBluetoothHeadset;
65
66 private PowerManager mPowerManager;
67
68 private WakeLock mStartCallWakeLock; // held while waiting for the intent to start call
69
70 private PhoneConstants.State mPhoneState = PhoneConstants.State.IDLE;
71 CdmaPhoneCallState.PhoneCallState mCdmaThreeWayCallState =
72 CdmaPhoneCallState.PhoneCallState.IDLE;
73
74 private Call.State mForegroundCallState;
75 private Call.State mRingingCallState;
76 private CallNumber mRingNumber;
77 // number of active calls
78 int mNumActive;
79 // number of background (held) calls
80 int mNumHeld;
81
82 long mBgndEarliestConnectionTime = 0;
83
84 // CDMA specific flag used in context with BT devices having display capabilities
85 // to show which Caller is active. This state might not be always true as in CDMA
86 // networks if a caller drops off no update is provided to the Phone.
87 // This flag is just used as a toggle to provide a update to the BT device to specify
88 // which caller is active.
89 private boolean mCdmaIsSecondCallActive = false;
90 private boolean mCdmaCallsSwapped = false;
91
92 private long[] mClccTimestamps; // Timestamps associated with each clcc index
93 private boolean[] mClccUsed; // Is this clcc index in use
94
95 private static final int GSM_MAX_CONNECTIONS = 6; // Max connections allowed by GSM
96 private static final int CDMA_MAX_CONNECTIONS = 2; // Max connections allowed by CDMA
97
98 @Override
99 public void onCreate() {
100 super.onCreate();
101 mCM = CallManager.getInstance();
102 mAdapter = BluetoothAdapter.getDefaultAdapter();
103 if (mAdapter == null) {
104 if (VDBG) Log.d(TAG, "mAdapter null");
105 return;
106 }
107
108 mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
109 mStartCallWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
110 TAG + ":StartCall");
111 mStartCallWakeLock.setReferenceCounted(false);
112
113 mAdapter.getProfileProxy(this, mProfileListener, BluetoothProfile.HEADSET);
114
115 mForegroundCallState = Call.State.IDLE;
116 mRingingCallState = Call.State.IDLE;
117 mNumActive = 0;
118 mNumHeld = 0;
119 mRingNumber = new CallNumber("", 0);;
120
121 handlePreciseCallStateChange(null);
122
123 if(VDBG) Log.d(TAG, "registerForServiceStateChanged");
124 // register for updates
Santos Cordon9611db72013-08-20 13:16:41 -0700125 mCM.registerForPreciseCallStateChanged(mHandler, PRECISE_CALL_STATE_CHANGED, null);
126 mCM.registerForCallWaiting(mHandler, PHONE_CDMA_CALL_WAITING, null);
127 mCM.registerForDisconnect(mHandler, PHONE_ON_DISCONNECT, null);
128
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700129 // TODO(BT) registerForIncomingRing?
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700130 mClccTimestamps = new long[GSM_MAX_CONNECTIONS];
131 mClccUsed = new boolean[GSM_MAX_CONNECTIONS];
132 for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
133 mClccUsed[i] = false;
134 }
135 }
136
137 @Override
138 public void onStart(Intent intent, int startId) {
139 if (mAdapter == null) {
140 Log.w(TAG, "Stopping Bluetooth BluetoothPhoneService Service: device does not have BT");
141 stopSelf();
142 }
143 if (VDBG) Log.d(TAG, "BluetoothPhoneService started");
144 }
145
146 @Override
147 public void onDestroy() {
148 super.onDestroy();
149 if (DBG) log("Stopping Bluetooth BluetoothPhoneService Service");
150 }
151
152 @Override
153 public IBinder onBind(Intent intent) {
154 return mBinder;
155 }
156
157 private static final int PRECISE_CALL_STATE_CHANGED = 1;
158 private static final int PHONE_CDMA_CALL_WAITING = 2;
159 private static final int LIST_CURRENT_CALLS = 3;
160 private static final int QUERY_PHONE_STATE = 4;
161 private static final int CDMA_SWAP_SECOND_CALL_STATE = 5;
162 private static final int CDMA_SET_SECOND_CALL_STATE = 6;
Santos Cordon9611db72013-08-20 13:16:41 -0700163 private static final int PHONE_ON_DISCONNECT = 7;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700164
165 private Handler mHandler = new Handler() {
166 @Override
167 public void handleMessage(Message msg) {
168 if (VDBG) Log.d(TAG, "handleMessage: " + msg.what);
169 switch(msg.what) {
170 case PRECISE_CALL_STATE_CHANGED:
171 case PHONE_CDMA_CALL_WAITING:
Santos Cordon9611db72013-08-20 13:16:41 -0700172 case PHONE_ON_DISCONNECT:
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700173 Connection connection = null;
174 if (((AsyncResult) msg.obj).result instanceof Connection) {
175 connection = (Connection) ((AsyncResult) msg.obj).result;
176 }
177 handlePreciseCallStateChange(connection);
178 break;
179 case LIST_CURRENT_CALLS:
180 handleListCurrentCalls();
181 break;
182 case QUERY_PHONE_STATE:
183 handleQueryPhoneState();
184 break;
185 case CDMA_SWAP_SECOND_CALL_STATE:
186 handleCdmaSwapSecondCallState();
187 break;
188 case CDMA_SET_SECOND_CALL_STATE:
189 handleCdmaSetSecondCallState((Boolean) msg.obj);
190 break;
191 }
192 }
193 };
194
195 private void updateBtPhoneStateAfterRadioTechnologyChange() {
196 if(VDBG) Log.d(TAG, "updateBtPhoneStateAfterRadioTechnologyChange...");
197
198 //Unregister all events from the old obsolete phone
199 mCM.unregisterForPreciseCallStateChanged(mHandler);
200 mCM.unregisterForCallWaiting(mHandler);
201
202 //Register all events new to the new active phone
203 mCM.registerForPreciseCallStateChanged(mHandler,
204 PRECISE_CALL_STATE_CHANGED, null);
205 mCM.registerForCallWaiting(mHandler,
206 PHONE_CDMA_CALL_WAITING, null);
207 }
208
209 private void handlePreciseCallStateChange(Connection connection) {
210 // get foreground call state
211 int oldNumActive = mNumActive;
212 int oldNumHeld = mNumHeld;
213 Call.State oldRingingCallState = mRingingCallState;
214 Call.State oldForegroundCallState = mForegroundCallState;
215 CallNumber oldRingNumber = mRingNumber;
216
217 Call foregroundCall = mCM.getActiveFgCall();
218
219 if (VDBG)
220 Log.d(TAG, " handlePreciseCallStateChange: foreground: " + foregroundCall +
221 " background: " + mCM.getFirstActiveBgCall() + " ringing: " +
222 mCM.getFirstActiveRingingCall());
223
224 mForegroundCallState = foregroundCall.getState();
225 /* if in transition, do not update */
226 if (mForegroundCallState == Call.State.DISCONNECTING)
227 {
228 Log.d(TAG, "handlePreciseCallStateChange. Call disconnecting, wait before update");
229 return;
230 }
231 else
232 mNumActive = (mForegroundCallState == Call.State.ACTIVE) ? 1 : 0;
233
234 Call ringingCall = mCM.getFirstActiveRingingCall();
235 mRingingCallState = ringingCall.getState();
236 mRingNumber = getCallNumber(connection, ringingCall);
237
238 if (mCM.getDefaultPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
239 mNumHeld = getNumHeldCdma();
240 PhoneGlobals app = PhoneGlobals.getInstance();
241 if (app.cdmaPhoneCallState != null) {
242 CdmaPhoneCallState.PhoneCallState currCdmaThreeWayCallState =
243 app.cdmaPhoneCallState.getCurrentCallState();
244 CdmaPhoneCallState.PhoneCallState prevCdmaThreeWayCallState =
245 app.cdmaPhoneCallState.getPreviousCallState();
246
247 log("CDMA call state: " + currCdmaThreeWayCallState + " prev state:" +
248 prevCdmaThreeWayCallState);
249
250 if ((mBluetoothHeadset != null) &&
251 (mCdmaThreeWayCallState != currCdmaThreeWayCallState)) {
252 // In CDMA, the network does not provide any feedback
253 // to the phone when the 2nd MO call goes through the
254 // stages of DIALING > ALERTING -> ACTIVE we fake the
255 // sequence
256 log("CDMA 3way call state change. mNumActive: " + mNumActive +
257 " mNumHeld: " + mNumHeld + " IsThreeWayCallOrigStateDialing: " +
258 app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing());
259 if ((currCdmaThreeWayCallState ==
260 CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
261 && app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing()) {
262 // Mimic dialing, put the call on hold, alerting
263 mBluetoothHeadset.phoneStateChanged(0, mNumHeld,
264 convertCallState(Call.State.IDLE, Call.State.DIALING),
265 mRingNumber.mNumber, mRingNumber.mType);
266
267 mBluetoothHeadset.phoneStateChanged(0, mNumHeld,
268 convertCallState(Call.State.IDLE, Call.State.ALERTING),
269 mRingNumber.mNumber, mRingNumber.mType);
270
271 }
272
273 // In CDMA, the network does not provide any feedback to
274 // the phone when a user merges a 3way call or swaps
275 // between two calls we need to send a CIEV response
276 // indicating that a call state got changed which should
277 // trigger a CLCC update request from the BT client.
278 if (currCdmaThreeWayCallState ==
279 CdmaPhoneCallState.PhoneCallState.CONF_CALL &&
280 prevCdmaThreeWayCallState ==
281 CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
282 log("CDMA 3way conf call. mNumActive: " + mNumActive +
283 " mNumHeld: " + mNumHeld);
284 mBluetoothHeadset.phoneStateChanged(mNumActive, mNumHeld,
285 convertCallState(Call.State.IDLE, mForegroundCallState),
286 mRingNumber.mNumber, mRingNumber.mType);
287 }
288 }
289 mCdmaThreeWayCallState = currCdmaThreeWayCallState;
290 }
291 } else {
292 mNumHeld = getNumHeldUmts();
293 }
294
295 boolean callsSwitched = false;
296 if (mCM.getDefaultPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA &&
297 mCdmaThreeWayCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
298 callsSwitched = mCdmaCallsSwapped;
299 } else {
300 Call backgroundCall = mCM.getFirstActiveBgCall();
301 callsSwitched =
302 (mNumHeld == 1 && ! (backgroundCall.getEarliestConnectTime() ==
303 mBgndEarliestConnectionTime));
304 mBgndEarliestConnectionTime = backgroundCall.getEarliestConnectTime();
305 }
306
307 if (mNumActive != oldNumActive || mNumHeld != oldNumHeld ||
308 mRingingCallState != oldRingingCallState ||
309 mForegroundCallState != oldForegroundCallState ||
310 !mRingNumber.equalTo(oldRingNumber) ||
311 callsSwitched) {
312 if (mBluetoothHeadset != null) {
313 mBluetoothHeadset.phoneStateChanged(mNumActive, mNumHeld,
314 convertCallState(mRingingCallState, mForegroundCallState),
315 mRingNumber.mNumber, mRingNumber.mType);
316 }
317 }
318 }
319
320 private void handleListCurrentCalls() {
321 Phone phone = mCM.getDefaultPhone();
322 int phoneType = phone.getPhoneType();
323
324 // TODO(BT) handle virtual call
325
326 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
327 listCurrentCallsCdma();
328 } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
329 listCurrentCallsGsm();
330 } else {
331 Log.e(TAG, "Unexpected phone type: " + phoneType);
332 }
333 // end the result
334 // when index is 0, other parameter does not matter
Zhihai Xu1c094812013-11-21 11:23:27 -0800335 if (mBluetoothHeadset != null) {
336 mBluetoothHeadset.clccResponse(0, 0, 0, 0, false, "", 0);
337 }
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700338 }
339
340 private void handleQueryPhoneState() {
341 if (mBluetoothHeadset != null) {
342 mBluetoothHeadset.phoneStateChanged(mNumActive, mNumHeld,
343 convertCallState(mRingingCallState, mForegroundCallState),
344 mRingNumber.mNumber, mRingNumber.mType);
345 }
346 }
347
348 private int getNumHeldUmts() {
349 int countHeld = 0;
350 List<Call> heldCalls = mCM.getBackgroundCalls();
351
352 for (Call call : heldCalls) {
353 if (call.getState() == Call.State.HOLDING) {
354 countHeld++;
355 }
356 }
357 return countHeld;
358 }
359
360 private int getNumHeldCdma() {
361 int numHeld = 0;
362 PhoneGlobals app = PhoneGlobals.getInstance();
363 if (app.cdmaPhoneCallState != null) {
364 CdmaPhoneCallState.PhoneCallState curr3WayCallState =
365 app.cdmaPhoneCallState.getCurrentCallState();
366 CdmaPhoneCallState.PhoneCallState prev3WayCallState =
367 app.cdmaPhoneCallState.getPreviousCallState();
368
369 log("CDMA call state: " + curr3WayCallState + " prev state:" +
370 prev3WayCallState);
371 if (curr3WayCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
372 if (prev3WayCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
373 numHeld = 0; //0: no calls held, as now *both* the caller are active
374 } else {
375 numHeld = 1; //1: held call and active call, as on answering a
376 // Call Waiting, one of the caller *is* put on hold
377 }
378 } else if (curr3WayCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
379 numHeld = 1; //1: held call and active call, as on make a 3 Way Call
380 // the first caller *is* put on hold
381 } else {
382 numHeld = 0; //0: no calls held as this is a SINGLE_ACTIVE call
383 }
384 }
385 return numHeld;
386 }
387
388 private CallNumber getCallNumber(Connection connection, Call call) {
389 String number = null;
390 int type = 128;
391 // find phone number and type
392 if (connection == null) {
393 connection = call.getEarliestConnection();
394 if (connection == null) {
395 Log.e(TAG, "Could not get a handle on Connection object for the call");
396 }
397 }
398 if (connection != null) {
399 number = connection.getAddress();
400 if (number != null) {
401 type = PhoneNumberUtils.toaFromString(number);
402 }
403 }
404 if (number == null) {
405 number = "";
406 }
407 return new CallNumber(number, type);
408 }
409
410 private class CallNumber
411 {
412 private String mNumber = null;
413 private int mType = 0;
414
415 private CallNumber(String number, int type) {
416 mNumber = number;
417 mType = type;
418 }
419
Santos Cordon9611db72013-08-20 13:16:41 -0700420 private boolean equalTo(CallNumber callNumber)
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700421 {
422 if (mType != callNumber.mType) return false;
Santos Cordon9611db72013-08-20 13:16:41 -0700423
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700424 if (mNumber != null && mNumber.compareTo(callNumber.mNumber) == 0) {
425 return true;
426 }
427 return false;
428 }
429 }
430
431 private BluetoothProfile.ServiceListener mProfileListener =
432 new BluetoothProfile.ServiceListener() {
433 public void onServiceConnected(int profile, BluetoothProfile proxy) {
434 mBluetoothHeadset = (BluetoothHeadset) proxy;
435 }
436 public void onServiceDisconnected(int profile) {
437 mBluetoothHeadset = null;
438 }
439 };
440
441 private void listCurrentCallsGsm() {
442 // Collect all known connections
443 // clccConnections isindexed by CLCC index
444 Connection[] clccConnections = new Connection[GSM_MAX_CONNECTIONS];
445 LinkedList<Connection> newConnections = new LinkedList<Connection>();
446 LinkedList<Connection> connections = new LinkedList<Connection>();
447
448 Call foregroundCall = mCM.getActiveFgCall();
449 Call backgroundCall = mCM.getFirstActiveBgCall();
450 Call ringingCall = mCM.getFirstActiveRingingCall();
451
452 if (ringingCall.getState().isAlive()) {
453 connections.addAll(ringingCall.getConnections());
454 }
455 if (foregroundCall.getState().isAlive()) {
456 connections.addAll(foregroundCall.getConnections());
457 }
458 if (backgroundCall.getState().isAlive()) {
459 connections.addAll(backgroundCall.getConnections());
460 }
461
462 // Mark connections that we already known about
463 boolean clccUsed[] = new boolean[GSM_MAX_CONNECTIONS];
464 for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
465 clccUsed[i] = mClccUsed[i];
466 mClccUsed[i] = false;
467 }
468 for (Connection c : connections) {
469 boolean found = false;
470 long timestamp = c.getCreateTime();
471 for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
472 if (clccUsed[i] && timestamp == mClccTimestamps[i]) {
473 mClccUsed[i] = true;
474 found = true;
475 clccConnections[i] = c;
476 break;
477 }
478 }
479 if (!found) {
480 newConnections.add(c);
481 }
482 }
483
484 // Find a CLCC index for new connections
485 while (!newConnections.isEmpty()) {
486 // Find lowest empty index
487 int i = 0;
488 while (mClccUsed[i]) i++;
489 // Find earliest connection
490 long earliestTimestamp = newConnections.get(0).getCreateTime();
491 Connection earliestConnection = newConnections.get(0);
492 for (int j = 0; j < newConnections.size(); j++) {
493 long timestamp = newConnections.get(j).getCreateTime();
494 if (timestamp < earliestTimestamp) {
495 earliestTimestamp = timestamp;
496 earliestConnection = newConnections.get(j);
497 }
498 }
499
500 // update
501 mClccUsed[i] = true;
502 mClccTimestamps[i] = earliestTimestamp;
503 clccConnections[i] = earliestConnection;
504 newConnections.remove(earliestConnection);
505 }
506
507 // Send CLCC response to Bluetooth headset service
508 for (int i = 0; i < clccConnections.length; i++) {
509 if (mClccUsed[i]) {
510 sendClccResponseGsm(i, clccConnections[i]);
511 }
512 }
513 }
514
515 /** Convert a Connection object into a single +CLCC result */
516 private void sendClccResponseGsm(int index, Connection connection) {
517 int state = convertCallState(connection.getState());
518 boolean mpty = false;
519 Call call = connection.getCall();
520 if (call != null) {
521 mpty = call.isMultiparty();
522 }
523
524 int direction = connection.isIncoming() ? 1 : 0;
525
526 String number = connection.getAddress();
527 int type = -1;
528 if (number != null) {
529 type = PhoneNumberUtils.toaFromString(number);
530 }
531
Zhihai Xu1c094812013-11-21 11:23:27 -0800532 if (mBluetoothHeadset != null) {
533 mBluetoothHeadset.clccResponse(index + 1, direction, state, 0, mpty, number, type);
534 }
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700535 }
536
537 /** Build the +CLCC result for CDMA
538 * The complexity arises from the fact that we need to maintain the same
539 * CLCC index even as a call moves between states. */
540 private synchronized void listCurrentCallsCdma() {
541 // In CDMA at one time a user can have only two live/active connections
542 Connection[] clccConnections = new Connection[CDMA_MAX_CONNECTIONS];// indexed by CLCC index
543 Call foregroundCall = mCM.getActiveFgCall();
544 Call ringingCall = mCM.getFirstActiveRingingCall();
545
546 Call.State ringingCallState = ringingCall.getState();
547 // If the Ringing Call state is INCOMING, that means this is the very first call
548 // hence there should not be any Foreground Call
549 if (ringingCallState == Call.State.INCOMING) {
550 if (VDBG) log("Filling clccConnections[0] for INCOMING state");
551 clccConnections[0] = ringingCall.getLatestConnection();
552 } else if (foregroundCall.getState().isAlive()) {
553 // Getting Foreground Call connection based on Call state
554 if (ringingCall.isRinging()) {
555 if (VDBG) log("Filling clccConnections[0] & [1] for CALL WAITING state");
556 clccConnections[0] = foregroundCall.getEarliestConnection();
557 clccConnections[1] = ringingCall.getLatestConnection();
558 } else {
559 if (foregroundCall.getConnections().size() <= 1) {
560 // Single call scenario
561 if (VDBG) {
562 log("Filling clccConnections[0] with ForgroundCall latest connection");
563 }
564 clccConnections[0] = foregroundCall.getLatestConnection();
565 } else {
566 // Multiple Call scenario. This would be true for both
567 // CONF_CALL and THRWAY_ACTIVE state
568 if (VDBG) {
569 log("Filling clccConnections[0] & [1] with ForgroundCall connections");
570 }
571 clccConnections[0] = foregroundCall.getEarliestConnection();
572 clccConnections[1] = foregroundCall.getLatestConnection();
573 }
574 }
575 }
576
577 // Update the mCdmaIsSecondCallActive flag based on the Phone call state
578 if (PhoneGlobals.getInstance().cdmaPhoneCallState.getCurrentCallState()
579 == CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE) {
580 Message msg = mHandler.obtainMessage(CDMA_SET_SECOND_CALL_STATE, false);
581 mHandler.sendMessage(msg);
582 } else if (PhoneGlobals.getInstance().cdmaPhoneCallState.getCurrentCallState()
583 == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
584 Message msg = mHandler.obtainMessage(CDMA_SET_SECOND_CALL_STATE, true);
585 mHandler.sendMessage(msg);
586 }
587
588 // send CLCC result
589 for (int i = 0; (i < clccConnections.length) && (clccConnections[i] != null); i++) {
590 sendClccResponseCdma(i, clccConnections[i]);
591 }
592 }
593
594 /** Send ClCC results for a Connection object for CDMA phone */
595 private void sendClccResponseCdma(int index, Connection connection) {
596 int state;
597 PhoneGlobals app = PhoneGlobals.getInstance();
598 CdmaPhoneCallState.PhoneCallState currCdmaCallState =
599 app.cdmaPhoneCallState.getCurrentCallState();
600 CdmaPhoneCallState.PhoneCallState prevCdmaCallState =
601 app.cdmaPhoneCallState.getPreviousCallState();
602
603 if ((prevCdmaCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
604 && (currCdmaCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL)) {
605 // If the current state is reached after merging two calls
606 // we set the state of all the connections as ACTIVE
607 state = CALL_STATE_ACTIVE;
608 } else {
609 Call.State callState = connection.getState();
610 switch (callState) {
611 case ACTIVE:
612 // For CDMA since both the connections are set as active by FW after accepting
613 // a Call waiting or making a 3 way call, we need to set the state specifically
614 // to ACTIVE/HOLDING based on the mCdmaIsSecondCallActive flag. This way the
615 // CLCC result will allow BT devices to enable the swap or merge options
616 if (index == 0) { // For the 1st active connection
617 state = mCdmaIsSecondCallActive ? CALL_STATE_HELD : CALL_STATE_ACTIVE;
618 } else { // for the 2nd active connection
619 state = mCdmaIsSecondCallActive ? CALL_STATE_ACTIVE : CALL_STATE_HELD;
620 }
621 break;
622 case HOLDING:
623 state = CALL_STATE_HELD;
624 break;
625 case DIALING:
626 state = CALL_STATE_DIALING;
627 break;
628 case ALERTING:
629 state = CALL_STATE_ALERTING;
630 break;
631 case INCOMING:
632 state = CALL_STATE_INCOMING;
633 break;
634 case WAITING:
635 state = CALL_STATE_WAITING;
636 break;
637 default:
638 Log.e(TAG, "bad call state: " + callState);
639 return;
640 }
641 }
642
643 boolean mpty = false;
644 if (currCdmaCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
645 if (prevCdmaCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
646 // If the current state is reached after merging two calls
647 // we set the multiparty call true.
648 mpty = true;
649 } // else
650 // CALL_CONF state is not from merging two calls, but from
651 // accepting the second call. In this case first will be on
652 // hold in most cases but in some cases its already merged.
653 // However, we will follow the common case and the test case
654 // as per Bluetooth SIG PTS
655 }
656
657 int direction = connection.isIncoming() ? 1 : 0;
658
659 String number = connection.getAddress();
660 int type = -1;
661 if (number != null) {
662 type = PhoneNumberUtils.toaFromString(number);
663 } else {
664 number = "";
665 }
666
Zhihai Xu1c094812013-11-21 11:23:27 -0800667 if (mBluetoothHeadset != null) {
668 mBluetoothHeadset.clccResponse(index + 1, direction, state, 0, mpty, number, type);
669 }
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700670 }
671
672 private void handleCdmaSwapSecondCallState() {
673 if (VDBG) log("cdmaSwapSecondCallState: Toggling mCdmaIsSecondCallActive");
674 mCdmaIsSecondCallActive = !mCdmaIsSecondCallActive;
675 mCdmaCallsSwapped = true;
676 }
677
678 private void handleCdmaSetSecondCallState(boolean state) {
679 if (VDBG) log("cdmaSetSecondCallState: Setting mCdmaIsSecondCallActive to " + state);
680 mCdmaIsSecondCallActive = state;
681
682 if (!mCdmaIsSecondCallActive) {
683 mCdmaCallsSwapped = false;
684 }
685 }
686
687 private final IBluetoothHeadsetPhone.Stub mBinder = new IBluetoothHeadsetPhone.Stub() {
688 public boolean answerCall() {
689 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
690 return PhoneUtils.answerCall(mCM.getFirstActiveRingingCall());
691 }
692
693 public boolean hangupCall() {
694 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
695 if (mCM.hasActiveFgCall()) {
696 return PhoneUtils.hangupActiveCall(mCM.getActiveFgCall());
697 } else if (mCM.hasActiveRingingCall()) {
698 return PhoneUtils.hangupRingingCall(mCM.getFirstActiveRingingCall());
699 } else if (mCM.hasActiveBgCall()) {
700 return PhoneUtils.hangupHoldingCall(mCM.getFirstActiveBgCall());
701 }
702 // TODO(BT) handle virtual voice call
703 return false;
704 }
705
706 public boolean sendDtmf(int dtmf) {
707 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
708 return mCM.sendDtmf((char) dtmf);
709 }
710
711 public boolean processChld(int chld) {
712 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
713 Phone phone = mCM.getDefaultPhone();
714 int phoneType = phone.getPhoneType();
715 Call ringingCall = mCM.getFirstActiveRingingCall();
716 Call backgroundCall = mCM.getFirstActiveBgCall();
717
718 if (chld == CHLD_TYPE_RELEASEHELD) {
719 if (ringingCall.isRinging()) {
720 return PhoneUtils.hangupRingingCall(ringingCall);
721 } else {
722 return PhoneUtils.hangupHoldingCall(backgroundCall);
723 }
724 } else if (chld == CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD) {
725 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
726 if (ringingCall.isRinging()) {
727 // Hangup the active call and then answer call waiting call.
728 if (VDBG) log("CHLD:1 Callwaiting Answer call");
729 PhoneUtils.hangupRingingAndActive(phone);
730 } else {
731 // If there is no Call waiting then just hangup
732 // the active call. In CDMA this mean that the complete
733 // call session would be ended
734 if (VDBG) log("CHLD:1 Hangup Call");
735 PhoneUtils.hangup(PhoneGlobals.getInstance().mCM);
736 }
737 return true;
738 } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
739 // Hangup active call, answer held call
740 return PhoneUtils.answerAndEndActive(PhoneGlobals.getInstance().mCM, ringingCall);
741 } else {
742 Log.e(TAG, "bad phone type: " + phoneType);
743 return false;
744 }
745 } else if (chld == CHLD_TYPE_HOLDACTIVE_ACCEPTHELD) {
746 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
747 // For CDMA, the way we switch to a new incoming call is by
748 // calling PhoneUtils.answerCall(). switchAndHoldActive() won't
749 // properly update the call state within telephony.
750 // If the Phone state is already in CONF_CALL then we simply send
751 // a flash cmd by calling switchHoldingAndActive()
752 if (ringingCall.isRinging()) {
753 if (VDBG) log("CHLD:2 Callwaiting Answer call");
754 PhoneUtils.answerCall(ringingCall);
755 PhoneUtils.setMute(false);
756 // Setting the second callers state flag to TRUE (i.e. active)
757 cdmaSetSecondCallState(true);
758 return true;
759 } else if (PhoneGlobals.getInstance().cdmaPhoneCallState
760 .getCurrentCallState()
761 == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
762 if (VDBG) log("CHLD:2 Swap Calls");
763 PhoneUtils.switchHoldingAndActive(backgroundCall);
764 // Toggle the second callers active state flag
765 cdmaSwapSecondCallState();
766 return true;
767 }
768 Log.e(TAG, "CDMA fail to do hold active and accept held");
769 return false;
770 } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
771 PhoneUtils.switchHoldingAndActive(backgroundCall);
772 return true;
773 } else {
774 Log.e(TAG, "Unexpected phone type: " + phoneType);
775 return false;
776 }
777 } else if (chld == CHLD_TYPE_ADDHELDTOCONF) {
778 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
779 CdmaPhoneCallState.PhoneCallState state =
780 PhoneGlobals.getInstance().cdmaPhoneCallState.getCurrentCallState();
781 // For CDMA, we need to check if the call is in THRWAY_ACTIVE state
782 if (state == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
783 if (VDBG) log("CHLD:3 Merge Calls");
784 PhoneUtils.mergeCalls();
785 return true;
786 } else if (state == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
787 // State is CONF_CALL already and we are getting a merge call
788 // This can happen when CONF_CALL was entered from a Call Waiting
789 // TODO(BT)
790 return false;
791 }
792 Log.e(TAG, "GSG no call to add conference");
793 return false;
794 } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
795 if (mCM.hasActiveFgCall() && mCM.hasActiveBgCall()) {
796 PhoneUtils.mergeCalls();
797 return true;
798 } else {
799 Log.e(TAG, "GSG no call to merge");
800 return false;
801 }
802 } else {
803 Log.e(TAG, "Unexpected phone type: " + phoneType);
804 return false;
Santos Cordon9611db72013-08-20 13:16:41 -0700805 }
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700806 } else {
807 Log.e(TAG, "bad CHLD value: " + chld);
808 return false;
809 }
810 }
811
812 public String getNetworkOperator() {
813 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
814 return mCM.getDefaultPhone().getServiceState().getOperatorAlphaLong();
815 }
816
817 public String getSubscriberNumber() {
818 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
819 return mCM.getDefaultPhone().getLine1Number();
820 }
821
822 public boolean listCurrentCalls() {
823 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
824 Message msg = Message.obtain(mHandler, LIST_CURRENT_CALLS);
825 mHandler.sendMessage(msg);
826 return true;
827 }
828
829 public boolean queryPhoneState() {
830 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
831 Message msg = Message.obtain(mHandler, QUERY_PHONE_STATE);
832 mHandler.sendMessage(msg);
833 return true;
834 }
835
836 public void updateBtHandsfreeAfterRadioTechnologyChange() {
837 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
838 if (VDBG) Log.d(TAG, "updateBtHandsfreeAfterRadioTechnologyChange...");
839 updateBtPhoneStateAfterRadioTechnologyChange();
840 }
841
842 public void cdmaSwapSecondCallState() {
843 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
844 Message msg = Message.obtain(mHandler, CDMA_SWAP_SECOND_CALL_STATE);
845 mHandler.sendMessage(msg);
846 }
847
848 public void cdmaSetSecondCallState(boolean state) {
849 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
850 Message msg = mHandler.obtainMessage(CDMA_SET_SECOND_CALL_STATE, state);
851 mHandler.sendMessage(msg);
852 }
853 };
854
855 // match up with bthf_call_state_t of bt_hf.h
856 final static int CALL_STATE_ACTIVE = 0;
857 final static int CALL_STATE_HELD = 1;
858 final static int CALL_STATE_DIALING = 2;
859 final static int CALL_STATE_ALERTING = 3;
860 final static int CALL_STATE_INCOMING = 4;
861 final static int CALL_STATE_WAITING = 5;
862 final static int CALL_STATE_IDLE = 6;
863
864 // match up with bthf_chld_type_t of bt_hf.h
865 final static int CHLD_TYPE_RELEASEHELD = 0;
866 final static int CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD = 1;
867 final static int CHLD_TYPE_HOLDACTIVE_ACCEPTHELD = 2;
868 final static int CHLD_TYPE_ADDHELDTOCONF = 3;
869
870 /* Convert telephony phone call state into hf hal call state */
871 static int convertCallState(Call.State ringingState, Call.State foregroundState) {
Santos Cordon9611db72013-08-20 13:16:41 -0700872 int retval = CALL_STATE_IDLE;
873
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700874 if ((ringingState == Call.State.INCOMING) ||
875 (ringingState == Call.State.WAITING) )
Santos Cordon9611db72013-08-20 13:16:41 -0700876 retval = CALL_STATE_INCOMING;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700877 else if (foregroundState == Call.State.DIALING)
Santos Cordon9611db72013-08-20 13:16:41 -0700878 retval = CALL_STATE_DIALING;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700879 else if (foregroundState == Call.State.ALERTING)
Santos Cordon9611db72013-08-20 13:16:41 -0700880 retval = CALL_STATE_ALERTING;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700881 else
Santos Cordon9611db72013-08-20 13:16:41 -0700882 retval = CALL_STATE_IDLE;
883
884 if (VDBG) {
885 Log.v(TAG, "Call state Converted2: " + ringingState + "/" + foregroundState + " -> " +
886 retval);
887 }
888 return retval;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700889 }
890
891 static int convertCallState(Call.State callState) {
Santos Cordon9611db72013-08-20 13:16:41 -0700892 int retval = CALL_STATE_IDLE;
893
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700894 switch (callState) {
895 case IDLE:
896 case DISCONNECTED:
897 case DISCONNECTING:
Santos Cordon9611db72013-08-20 13:16:41 -0700898 retval = CALL_STATE_IDLE;
Santos Cordone0585f32013-08-23 11:31:05 -0700899 break;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700900 case ACTIVE:
Santos Cordon9611db72013-08-20 13:16:41 -0700901 retval = CALL_STATE_ACTIVE;
Santos Cordone0585f32013-08-23 11:31:05 -0700902 break;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700903 case HOLDING:
Santos Cordon9611db72013-08-20 13:16:41 -0700904 retval = CALL_STATE_HELD;
Santos Cordone0585f32013-08-23 11:31:05 -0700905 break;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700906 case DIALING:
Santos Cordon9611db72013-08-20 13:16:41 -0700907 retval = CALL_STATE_DIALING;
Santos Cordone0585f32013-08-23 11:31:05 -0700908 break;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700909 case ALERTING:
Santos Cordon9611db72013-08-20 13:16:41 -0700910 retval = CALL_STATE_ALERTING;
Santos Cordone0585f32013-08-23 11:31:05 -0700911 break;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700912 case INCOMING:
Santos Cordon9611db72013-08-20 13:16:41 -0700913 retval = CALL_STATE_INCOMING;
Santos Cordone0585f32013-08-23 11:31:05 -0700914 break;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700915 case WAITING:
Santos Cordon9611db72013-08-20 13:16:41 -0700916 retval = CALL_STATE_WAITING;
Santos Cordone0585f32013-08-23 11:31:05 -0700917 break;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700918 default:
919 Log.e(TAG, "bad call state: " + callState);
Santos Cordon9611db72013-08-20 13:16:41 -0700920 retval = CALL_STATE_IDLE;
Santos Cordone0585f32013-08-23 11:31:05 -0700921 break;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700922 }
Santos Cordon9611db72013-08-20 13:16:41 -0700923
924 if (VDBG) {
925 Log.v(TAG, "Call state Converted2: " + callState + " -> " + retval);
926 }
927
928 return retval;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700929 }
930
931 private static void log(String msg) {
932 Log.d(TAG, msg);
933 }
934}