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