blob: 2211098acbee3f1cca659980c7e885730f7a028a [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
335 mBluetoothHeadset.clccResponse(0, 0, 0, 0, false, "", 0);
336 }
337
338 private void handleQueryPhoneState() {
339 if (mBluetoothHeadset != null) {
340 mBluetoothHeadset.phoneStateChanged(mNumActive, mNumHeld,
341 convertCallState(mRingingCallState, mForegroundCallState),
342 mRingNumber.mNumber, mRingNumber.mType);
343 }
344 }
345
346 private int getNumHeldUmts() {
347 int countHeld = 0;
348 List<Call> heldCalls = mCM.getBackgroundCalls();
349
350 for (Call call : heldCalls) {
351 if (call.getState() == Call.State.HOLDING) {
352 countHeld++;
353 }
354 }
355 return countHeld;
356 }
357
358 private int getNumHeldCdma() {
359 int numHeld = 0;
360 PhoneGlobals app = PhoneGlobals.getInstance();
361 if (app.cdmaPhoneCallState != null) {
362 CdmaPhoneCallState.PhoneCallState curr3WayCallState =
363 app.cdmaPhoneCallState.getCurrentCallState();
364 CdmaPhoneCallState.PhoneCallState prev3WayCallState =
365 app.cdmaPhoneCallState.getPreviousCallState();
366
367 log("CDMA call state: " + curr3WayCallState + " prev state:" +
368 prev3WayCallState);
369 if (curr3WayCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
370 if (prev3WayCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
371 numHeld = 0; //0: no calls held, as now *both* the caller are active
372 } else {
373 numHeld = 1; //1: held call and active call, as on answering a
374 // Call Waiting, one of the caller *is* put on hold
375 }
376 } else if (curr3WayCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
377 numHeld = 1; //1: held call and active call, as on make a 3 Way Call
378 // the first caller *is* put on hold
379 } else {
380 numHeld = 0; //0: no calls held as this is a SINGLE_ACTIVE call
381 }
382 }
383 return numHeld;
384 }
385
386 private CallNumber getCallNumber(Connection connection, Call call) {
387 String number = null;
388 int type = 128;
389 // find phone number and type
390 if (connection == null) {
391 connection = call.getEarliestConnection();
392 if (connection == null) {
393 Log.e(TAG, "Could not get a handle on Connection object for the call");
394 }
395 }
396 if (connection != null) {
397 number = connection.getAddress();
398 if (number != null) {
399 type = PhoneNumberUtils.toaFromString(number);
400 }
401 }
402 if (number == null) {
403 number = "";
404 }
405 return new CallNumber(number, type);
406 }
407
408 private class CallNumber
409 {
410 private String mNumber = null;
411 private int mType = 0;
412
413 private CallNumber(String number, int type) {
414 mNumber = number;
415 mType = type;
416 }
417
Santos Cordon9611db72013-08-20 13:16:41 -0700418 private boolean equalTo(CallNumber callNumber)
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700419 {
420 if (mType != callNumber.mType) return false;
Santos Cordon9611db72013-08-20 13:16:41 -0700421
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700422 if (mNumber != null && mNumber.compareTo(callNumber.mNumber) == 0) {
423 return true;
424 }
425 return false;
426 }
427 }
428
429 private BluetoothProfile.ServiceListener mProfileListener =
430 new BluetoothProfile.ServiceListener() {
431 public void onServiceConnected(int profile, BluetoothProfile proxy) {
432 mBluetoothHeadset = (BluetoothHeadset) proxy;
433 }
434 public void onServiceDisconnected(int profile) {
435 mBluetoothHeadset = null;
436 }
437 };
438
439 private void listCurrentCallsGsm() {
440 // Collect all known connections
441 // clccConnections isindexed by CLCC index
442 Connection[] clccConnections = new Connection[GSM_MAX_CONNECTIONS];
443 LinkedList<Connection> newConnections = new LinkedList<Connection>();
444 LinkedList<Connection> connections = new LinkedList<Connection>();
445
446 Call foregroundCall = mCM.getActiveFgCall();
447 Call backgroundCall = mCM.getFirstActiveBgCall();
448 Call ringingCall = mCM.getFirstActiveRingingCall();
449
450 if (ringingCall.getState().isAlive()) {
451 connections.addAll(ringingCall.getConnections());
452 }
453 if (foregroundCall.getState().isAlive()) {
454 connections.addAll(foregroundCall.getConnections());
455 }
456 if (backgroundCall.getState().isAlive()) {
457 connections.addAll(backgroundCall.getConnections());
458 }
459
460 // Mark connections that we already known about
461 boolean clccUsed[] = new boolean[GSM_MAX_CONNECTIONS];
462 for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
463 clccUsed[i] = mClccUsed[i];
464 mClccUsed[i] = false;
465 }
466 for (Connection c : connections) {
467 boolean found = false;
468 long timestamp = c.getCreateTime();
469 for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
470 if (clccUsed[i] && timestamp == mClccTimestamps[i]) {
471 mClccUsed[i] = true;
472 found = true;
473 clccConnections[i] = c;
474 break;
475 }
476 }
477 if (!found) {
478 newConnections.add(c);
479 }
480 }
481
482 // Find a CLCC index for new connections
483 while (!newConnections.isEmpty()) {
484 // Find lowest empty index
485 int i = 0;
486 while (mClccUsed[i]) i++;
487 // Find earliest connection
488 long earliestTimestamp = newConnections.get(0).getCreateTime();
489 Connection earliestConnection = newConnections.get(0);
490 for (int j = 0; j < newConnections.size(); j++) {
491 long timestamp = newConnections.get(j).getCreateTime();
492 if (timestamp < earliestTimestamp) {
493 earliestTimestamp = timestamp;
494 earliestConnection = newConnections.get(j);
495 }
496 }
497
498 // update
499 mClccUsed[i] = true;
500 mClccTimestamps[i] = earliestTimestamp;
501 clccConnections[i] = earliestConnection;
502 newConnections.remove(earliestConnection);
503 }
504
505 // Send CLCC response to Bluetooth headset service
506 for (int i = 0; i < clccConnections.length; i++) {
507 if (mClccUsed[i]) {
508 sendClccResponseGsm(i, clccConnections[i]);
509 }
510 }
511 }
512
513 /** Convert a Connection object into a single +CLCC result */
514 private void sendClccResponseGsm(int index, Connection connection) {
515 int state = convertCallState(connection.getState());
516 boolean mpty = false;
517 Call call = connection.getCall();
518 if (call != null) {
519 mpty = call.isMultiparty();
520 }
521
522 int direction = connection.isIncoming() ? 1 : 0;
523
524 String number = connection.getAddress();
525 int type = -1;
526 if (number != null) {
527 type = PhoneNumberUtils.toaFromString(number);
528 }
529
530 mBluetoothHeadset.clccResponse(index + 1, direction, state, 0, mpty, number, type);
531 }
532
533 /** Build the +CLCC result for CDMA
534 * The complexity arises from the fact that we need to maintain the same
535 * CLCC index even as a call moves between states. */
536 private synchronized void listCurrentCallsCdma() {
537 // In CDMA at one time a user can have only two live/active connections
538 Connection[] clccConnections = new Connection[CDMA_MAX_CONNECTIONS];// indexed by CLCC index
539 Call foregroundCall = mCM.getActiveFgCall();
540 Call ringingCall = mCM.getFirstActiveRingingCall();
541
542 Call.State ringingCallState = ringingCall.getState();
543 // If the Ringing Call state is INCOMING, that means this is the very first call
544 // hence there should not be any Foreground Call
545 if (ringingCallState == Call.State.INCOMING) {
546 if (VDBG) log("Filling clccConnections[0] for INCOMING state");
547 clccConnections[0] = ringingCall.getLatestConnection();
548 } else if (foregroundCall.getState().isAlive()) {
549 // Getting Foreground Call connection based on Call state
550 if (ringingCall.isRinging()) {
551 if (VDBG) log("Filling clccConnections[0] & [1] for CALL WAITING state");
552 clccConnections[0] = foregroundCall.getEarliestConnection();
553 clccConnections[1] = ringingCall.getLatestConnection();
554 } else {
555 if (foregroundCall.getConnections().size() <= 1) {
556 // Single call scenario
557 if (VDBG) {
558 log("Filling clccConnections[0] with ForgroundCall latest connection");
559 }
560 clccConnections[0] = foregroundCall.getLatestConnection();
561 } else {
562 // Multiple Call scenario. This would be true for both
563 // CONF_CALL and THRWAY_ACTIVE state
564 if (VDBG) {
565 log("Filling clccConnections[0] & [1] with ForgroundCall connections");
566 }
567 clccConnections[0] = foregroundCall.getEarliestConnection();
568 clccConnections[1] = foregroundCall.getLatestConnection();
569 }
570 }
571 }
572
573 // Update the mCdmaIsSecondCallActive flag based on the Phone call state
574 if (PhoneGlobals.getInstance().cdmaPhoneCallState.getCurrentCallState()
575 == CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE) {
576 Message msg = mHandler.obtainMessage(CDMA_SET_SECOND_CALL_STATE, false);
577 mHandler.sendMessage(msg);
578 } else if (PhoneGlobals.getInstance().cdmaPhoneCallState.getCurrentCallState()
579 == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
580 Message msg = mHandler.obtainMessage(CDMA_SET_SECOND_CALL_STATE, true);
581 mHandler.sendMessage(msg);
582 }
583
584 // send CLCC result
585 for (int i = 0; (i < clccConnections.length) && (clccConnections[i] != null); i++) {
586 sendClccResponseCdma(i, clccConnections[i]);
587 }
588 }
589
590 /** Send ClCC results for a Connection object for CDMA phone */
591 private void sendClccResponseCdma(int index, Connection connection) {
592 int state;
593 PhoneGlobals app = PhoneGlobals.getInstance();
594 CdmaPhoneCallState.PhoneCallState currCdmaCallState =
595 app.cdmaPhoneCallState.getCurrentCallState();
596 CdmaPhoneCallState.PhoneCallState prevCdmaCallState =
597 app.cdmaPhoneCallState.getPreviousCallState();
598
599 if ((prevCdmaCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
600 && (currCdmaCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL)) {
601 // If the current state is reached after merging two calls
602 // we set the state of all the connections as ACTIVE
603 state = CALL_STATE_ACTIVE;
604 } else {
605 Call.State callState = connection.getState();
606 switch (callState) {
607 case ACTIVE:
608 // For CDMA since both the connections are set as active by FW after accepting
609 // a Call waiting or making a 3 way call, we need to set the state specifically
610 // to ACTIVE/HOLDING based on the mCdmaIsSecondCallActive flag. This way the
611 // CLCC result will allow BT devices to enable the swap or merge options
612 if (index == 0) { // For the 1st active connection
613 state = mCdmaIsSecondCallActive ? CALL_STATE_HELD : CALL_STATE_ACTIVE;
614 } else { // for the 2nd active connection
615 state = mCdmaIsSecondCallActive ? CALL_STATE_ACTIVE : CALL_STATE_HELD;
616 }
617 break;
618 case HOLDING:
619 state = CALL_STATE_HELD;
620 break;
621 case DIALING:
622 state = CALL_STATE_DIALING;
623 break;
624 case ALERTING:
625 state = CALL_STATE_ALERTING;
626 break;
627 case INCOMING:
628 state = CALL_STATE_INCOMING;
629 break;
630 case WAITING:
631 state = CALL_STATE_WAITING;
632 break;
633 default:
634 Log.e(TAG, "bad call state: " + callState);
635 return;
636 }
637 }
638
639 boolean mpty = false;
640 if (currCdmaCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
641 if (prevCdmaCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
642 // If the current state is reached after merging two calls
643 // we set the multiparty call true.
644 mpty = true;
645 } // else
646 // CALL_CONF state is not from merging two calls, but from
647 // accepting the second call. In this case first will be on
648 // hold in most cases but in some cases its already merged.
649 // However, we will follow the common case and the test case
650 // as per Bluetooth SIG PTS
651 }
652
653 int direction = connection.isIncoming() ? 1 : 0;
654
655 String number = connection.getAddress();
656 int type = -1;
657 if (number != null) {
658 type = PhoneNumberUtils.toaFromString(number);
659 } else {
660 number = "";
661 }
662
663 mBluetoothHeadset.clccResponse(index + 1, direction, state, 0, mpty, number, type);
664 }
665
666 private void handleCdmaSwapSecondCallState() {
667 if (VDBG) log("cdmaSwapSecondCallState: Toggling mCdmaIsSecondCallActive");
668 mCdmaIsSecondCallActive = !mCdmaIsSecondCallActive;
669 mCdmaCallsSwapped = true;
670 }
671
672 private void handleCdmaSetSecondCallState(boolean state) {
673 if (VDBG) log("cdmaSetSecondCallState: Setting mCdmaIsSecondCallActive to " + state);
674 mCdmaIsSecondCallActive = state;
675
676 if (!mCdmaIsSecondCallActive) {
677 mCdmaCallsSwapped = false;
678 }
679 }
680
681 private final IBluetoothHeadsetPhone.Stub mBinder = new IBluetoothHeadsetPhone.Stub() {
682 public boolean answerCall() {
683 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
684 return PhoneUtils.answerCall(mCM.getFirstActiveRingingCall());
685 }
686
687 public boolean hangupCall() {
688 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
689 if (mCM.hasActiveFgCall()) {
690 return PhoneUtils.hangupActiveCall(mCM.getActiveFgCall());
691 } else if (mCM.hasActiveRingingCall()) {
692 return PhoneUtils.hangupRingingCall(mCM.getFirstActiveRingingCall());
693 } else if (mCM.hasActiveBgCall()) {
694 return PhoneUtils.hangupHoldingCall(mCM.getFirstActiveBgCall());
695 }
696 // TODO(BT) handle virtual voice call
697 return false;
698 }
699
700 public boolean sendDtmf(int dtmf) {
701 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
702 return mCM.sendDtmf((char) dtmf);
703 }
704
705 public boolean processChld(int chld) {
706 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
707 Phone phone = mCM.getDefaultPhone();
708 int phoneType = phone.getPhoneType();
709 Call ringingCall = mCM.getFirstActiveRingingCall();
710 Call backgroundCall = mCM.getFirstActiveBgCall();
711
712 if (chld == CHLD_TYPE_RELEASEHELD) {
713 if (ringingCall.isRinging()) {
714 return PhoneUtils.hangupRingingCall(ringingCall);
715 } else {
716 return PhoneUtils.hangupHoldingCall(backgroundCall);
717 }
718 } else if (chld == CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD) {
719 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
720 if (ringingCall.isRinging()) {
721 // Hangup the active call and then answer call waiting call.
722 if (VDBG) log("CHLD:1 Callwaiting Answer call");
723 PhoneUtils.hangupRingingAndActive(phone);
724 } else {
725 // If there is no Call waiting then just hangup
726 // the active call. In CDMA this mean that the complete
727 // call session would be ended
728 if (VDBG) log("CHLD:1 Hangup Call");
729 PhoneUtils.hangup(PhoneGlobals.getInstance().mCM);
730 }
731 return true;
732 } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
733 // Hangup active call, answer held call
734 return PhoneUtils.answerAndEndActive(PhoneGlobals.getInstance().mCM, ringingCall);
735 } else {
736 Log.e(TAG, "bad phone type: " + phoneType);
737 return false;
738 }
739 } else if (chld == CHLD_TYPE_HOLDACTIVE_ACCEPTHELD) {
740 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
741 // For CDMA, the way we switch to a new incoming call is by
742 // calling PhoneUtils.answerCall(). switchAndHoldActive() won't
743 // properly update the call state within telephony.
744 // If the Phone state is already in CONF_CALL then we simply send
745 // a flash cmd by calling switchHoldingAndActive()
746 if (ringingCall.isRinging()) {
747 if (VDBG) log("CHLD:2 Callwaiting Answer call");
748 PhoneUtils.answerCall(ringingCall);
749 PhoneUtils.setMute(false);
750 // Setting the second callers state flag to TRUE (i.e. active)
751 cdmaSetSecondCallState(true);
752 return true;
753 } else if (PhoneGlobals.getInstance().cdmaPhoneCallState
754 .getCurrentCallState()
755 == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
756 if (VDBG) log("CHLD:2 Swap Calls");
757 PhoneUtils.switchHoldingAndActive(backgroundCall);
758 // Toggle the second callers active state flag
759 cdmaSwapSecondCallState();
760 return true;
761 }
762 Log.e(TAG, "CDMA fail to do hold active and accept held");
763 return false;
764 } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
765 PhoneUtils.switchHoldingAndActive(backgroundCall);
766 return true;
767 } else {
768 Log.e(TAG, "Unexpected phone type: " + phoneType);
769 return false;
770 }
771 } else if (chld == CHLD_TYPE_ADDHELDTOCONF) {
772 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
773 CdmaPhoneCallState.PhoneCallState state =
774 PhoneGlobals.getInstance().cdmaPhoneCallState.getCurrentCallState();
775 // For CDMA, we need to check if the call is in THRWAY_ACTIVE state
776 if (state == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
777 if (VDBG) log("CHLD:3 Merge Calls");
778 PhoneUtils.mergeCalls();
779 return true;
780 } else if (state == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
781 // State is CONF_CALL already and we are getting a merge call
782 // This can happen when CONF_CALL was entered from a Call Waiting
783 // TODO(BT)
784 return false;
785 }
786 Log.e(TAG, "GSG no call to add conference");
787 return false;
788 } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
789 if (mCM.hasActiveFgCall() && mCM.hasActiveBgCall()) {
790 PhoneUtils.mergeCalls();
791 return true;
792 } else {
793 Log.e(TAG, "GSG no call to merge");
794 return false;
795 }
796 } else {
797 Log.e(TAG, "Unexpected phone type: " + phoneType);
798 return false;
Santos Cordon9611db72013-08-20 13:16:41 -0700799 }
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700800 } else {
801 Log.e(TAG, "bad CHLD value: " + chld);
802 return false;
803 }
804 }
805
806 public String getNetworkOperator() {
807 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
808 return mCM.getDefaultPhone().getServiceState().getOperatorAlphaLong();
809 }
810
811 public String getSubscriberNumber() {
812 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
813 return mCM.getDefaultPhone().getLine1Number();
814 }
815
816 public boolean listCurrentCalls() {
817 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
818 Message msg = Message.obtain(mHandler, LIST_CURRENT_CALLS);
819 mHandler.sendMessage(msg);
820 return true;
821 }
822
823 public boolean queryPhoneState() {
824 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
825 Message msg = Message.obtain(mHandler, QUERY_PHONE_STATE);
826 mHandler.sendMessage(msg);
827 return true;
828 }
829
830 public void updateBtHandsfreeAfterRadioTechnologyChange() {
831 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
832 if (VDBG) Log.d(TAG, "updateBtHandsfreeAfterRadioTechnologyChange...");
833 updateBtPhoneStateAfterRadioTechnologyChange();
834 }
835
836 public void cdmaSwapSecondCallState() {
837 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
838 Message msg = Message.obtain(mHandler, CDMA_SWAP_SECOND_CALL_STATE);
839 mHandler.sendMessage(msg);
840 }
841
842 public void cdmaSetSecondCallState(boolean state) {
843 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
844 Message msg = mHandler.obtainMessage(CDMA_SET_SECOND_CALL_STATE, state);
845 mHandler.sendMessage(msg);
846 }
847 };
848
849 // match up with bthf_call_state_t of bt_hf.h
850 final static int CALL_STATE_ACTIVE = 0;
851 final static int CALL_STATE_HELD = 1;
852 final static int CALL_STATE_DIALING = 2;
853 final static int CALL_STATE_ALERTING = 3;
854 final static int CALL_STATE_INCOMING = 4;
855 final static int CALL_STATE_WAITING = 5;
856 final static int CALL_STATE_IDLE = 6;
857
858 // match up with bthf_chld_type_t of bt_hf.h
859 final static int CHLD_TYPE_RELEASEHELD = 0;
860 final static int CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD = 1;
861 final static int CHLD_TYPE_HOLDACTIVE_ACCEPTHELD = 2;
862 final static int CHLD_TYPE_ADDHELDTOCONF = 3;
863
864 /* Convert telephony phone call state into hf hal call state */
865 static int convertCallState(Call.State ringingState, Call.State foregroundState) {
Santos Cordon9611db72013-08-20 13:16:41 -0700866 int retval = CALL_STATE_IDLE;
867
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700868 if ((ringingState == Call.State.INCOMING) ||
869 (ringingState == Call.State.WAITING) )
Santos Cordon9611db72013-08-20 13:16:41 -0700870 retval = CALL_STATE_INCOMING;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700871 else if (foregroundState == Call.State.DIALING)
Santos Cordon9611db72013-08-20 13:16:41 -0700872 retval = CALL_STATE_DIALING;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700873 else if (foregroundState == Call.State.ALERTING)
Santos Cordon9611db72013-08-20 13:16:41 -0700874 retval = CALL_STATE_ALERTING;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700875 else
Santos Cordon9611db72013-08-20 13:16:41 -0700876 retval = CALL_STATE_IDLE;
877
878 if (VDBG) {
879 Log.v(TAG, "Call state Converted2: " + ringingState + "/" + foregroundState + " -> " +
880 retval);
881 }
882 return retval;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700883 }
884
885 static int convertCallState(Call.State callState) {
Santos Cordon9611db72013-08-20 13:16:41 -0700886 int retval = CALL_STATE_IDLE;
887
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700888 switch (callState) {
889 case IDLE:
890 case DISCONNECTED:
891 case DISCONNECTING:
Santos Cordon9611db72013-08-20 13:16:41 -0700892 retval = CALL_STATE_IDLE;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700893 case ACTIVE:
Santos Cordon9611db72013-08-20 13:16:41 -0700894 retval = CALL_STATE_ACTIVE;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700895 case HOLDING:
Santos Cordon9611db72013-08-20 13:16:41 -0700896 retval = CALL_STATE_HELD;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700897 case DIALING:
Santos Cordon9611db72013-08-20 13:16:41 -0700898 retval = CALL_STATE_DIALING;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700899 case ALERTING:
Santos Cordon9611db72013-08-20 13:16:41 -0700900 retval = CALL_STATE_ALERTING;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700901 case INCOMING:
Santos Cordon9611db72013-08-20 13:16:41 -0700902 retval = CALL_STATE_INCOMING;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700903 case WAITING:
Santos Cordon9611db72013-08-20 13:16:41 -0700904 retval = CALL_STATE_WAITING;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700905 default:
906 Log.e(TAG, "bad call state: " + callState);
Santos Cordon9611db72013-08-20 13:16:41 -0700907 retval = CALL_STATE_IDLE;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700908 }
Santos Cordon9611db72013-08-20 13:16:41 -0700909
910 if (VDBG) {
911 Log.v(TAG, "Call state Converted2: " + callState + " -> " + retval);
912 }
913
914 return retval;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700915 }
916
917 private static void log(String msg) {
918 Log.d(TAG, msg);
919 }
920}