Blanket copy of PhoneApp to services/Telephony.
First phase of splitting out InCallUI from PhoneApp.
Change-Id: I237341c4ff00e96c677caa4580b251ef3432931b
diff --git a/src/com/android/phone/BluetoothPhoneService.java b/src/com/android/phone/BluetoothPhoneService.java
new file mode 100644
index 0000000..aff6bf2
--- /dev/null
+++ b/src/com/android/phone/BluetoothPhoneService.java
@@ -0,0 +1,903 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import android.app.Service;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.IBluetoothHeadsetPhone;
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.SystemProperties;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.ServiceState;
+import android.util.Log;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.CallManager;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Bluetooth headset manager for the Phone app.
+ * @hide
+ */
+public class BluetoothPhoneService extends Service {
+ private static final String TAG = "BluetoothPhoneService";
+ private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 1)
+ && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+ private static final boolean VDBG = (PhoneGlobals.DBG_LEVEL >= 2); // even more logging
+
+ private static final String MODIFY_PHONE_STATE = android.Manifest.permission.MODIFY_PHONE_STATE;
+
+ private BluetoothAdapter mAdapter;
+ private CallManager mCM;
+
+ private BluetoothHeadset mBluetoothHeadset;
+
+ private PowerManager mPowerManager;
+
+ private WakeLock mStartCallWakeLock; // held while waiting for the intent to start call
+
+ private PhoneConstants.State mPhoneState = PhoneConstants.State.IDLE;
+ CdmaPhoneCallState.PhoneCallState mCdmaThreeWayCallState =
+ CdmaPhoneCallState.PhoneCallState.IDLE;
+
+ private Call.State mForegroundCallState;
+ private Call.State mRingingCallState;
+ private CallNumber mRingNumber;
+ // number of active calls
+ int mNumActive;
+ // number of background (held) calls
+ int mNumHeld;
+
+ long mBgndEarliestConnectionTime = 0;
+
+ // CDMA specific flag used in context with BT devices having display capabilities
+ // to show which Caller is active. This state might not be always true as in CDMA
+ // networks if a caller drops off no update is provided to the Phone.
+ // This flag is just used as a toggle to provide a update to the BT device to specify
+ // which caller is active.
+ private boolean mCdmaIsSecondCallActive = false;
+ private boolean mCdmaCallsSwapped = false;
+
+ private long[] mClccTimestamps; // Timestamps associated with each clcc index
+ private boolean[] mClccUsed; // Is this clcc index in use
+
+ private static final int GSM_MAX_CONNECTIONS = 6; // Max connections allowed by GSM
+ private static final int CDMA_MAX_CONNECTIONS = 2; // Max connections allowed by CDMA
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mCM = CallManager.getInstance();
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (mAdapter == null) {
+ if (VDBG) Log.d(TAG, "mAdapter null");
+ return;
+ }
+
+ mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ mStartCallWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ TAG + ":StartCall");
+ mStartCallWakeLock.setReferenceCounted(false);
+
+ mAdapter.getProfileProxy(this, mProfileListener, BluetoothProfile.HEADSET);
+
+ mForegroundCallState = Call.State.IDLE;
+ mRingingCallState = Call.State.IDLE;
+ mNumActive = 0;
+ mNumHeld = 0;
+ mRingNumber = new CallNumber("", 0);;
+
+ handlePreciseCallStateChange(null);
+
+ if(VDBG) Log.d(TAG, "registerForServiceStateChanged");
+ // register for updates
+ mCM.registerForPreciseCallStateChanged(mHandler,
+ PRECISE_CALL_STATE_CHANGED, null);
+ mCM.registerForCallWaiting(mHandler,
+ PHONE_CDMA_CALL_WAITING, null);
+ // TODO(BT) registerForIncomingRing?
+ // TODO(BT) registerdisconnection?
+ mClccTimestamps = new long[GSM_MAX_CONNECTIONS];
+ mClccUsed = new boolean[GSM_MAX_CONNECTIONS];
+ for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
+ mClccUsed[i] = false;
+ }
+ }
+
+ @Override
+ public void onStart(Intent intent, int startId) {
+ if (mAdapter == null) {
+ Log.w(TAG, "Stopping Bluetooth BluetoothPhoneService Service: device does not have BT");
+ stopSelf();
+ }
+ if (VDBG) Log.d(TAG, "BluetoothPhoneService started");
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (DBG) log("Stopping Bluetooth BluetoothPhoneService Service");
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ private static final int PRECISE_CALL_STATE_CHANGED = 1;
+ private static final int PHONE_CDMA_CALL_WAITING = 2;
+ private static final int LIST_CURRENT_CALLS = 3;
+ private static final int QUERY_PHONE_STATE = 4;
+ private static final int CDMA_SWAP_SECOND_CALL_STATE = 5;
+ private static final int CDMA_SET_SECOND_CALL_STATE = 6;
+
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (VDBG) Log.d(TAG, "handleMessage: " + msg.what);
+ switch(msg.what) {
+ case PRECISE_CALL_STATE_CHANGED:
+ case PHONE_CDMA_CALL_WAITING:
+ Connection connection = null;
+ if (((AsyncResult) msg.obj).result instanceof Connection) {
+ connection = (Connection) ((AsyncResult) msg.obj).result;
+ }
+ handlePreciseCallStateChange(connection);
+ break;
+ case LIST_CURRENT_CALLS:
+ handleListCurrentCalls();
+ break;
+ case QUERY_PHONE_STATE:
+ handleQueryPhoneState();
+ break;
+ case CDMA_SWAP_SECOND_CALL_STATE:
+ handleCdmaSwapSecondCallState();
+ break;
+ case CDMA_SET_SECOND_CALL_STATE:
+ handleCdmaSetSecondCallState((Boolean) msg.obj);
+ break;
+ }
+ }
+ };
+
+ private void updateBtPhoneStateAfterRadioTechnologyChange() {
+ if(VDBG) Log.d(TAG, "updateBtPhoneStateAfterRadioTechnologyChange...");
+
+ //Unregister all events from the old obsolete phone
+ mCM.unregisterForPreciseCallStateChanged(mHandler);
+ mCM.unregisterForCallWaiting(mHandler);
+
+ //Register all events new to the new active phone
+ mCM.registerForPreciseCallStateChanged(mHandler,
+ PRECISE_CALL_STATE_CHANGED, null);
+ mCM.registerForCallWaiting(mHandler,
+ PHONE_CDMA_CALL_WAITING, null);
+ }
+
+ private void handlePreciseCallStateChange(Connection connection) {
+ // get foreground call state
+ int oldNumActive = mNumActive;
+ int oldNumHeld = mNumHeld;
+ Call.State oldRingingCallState = mRingingCallState;
+ Call.State oldForegroundCallState = mForegroundCallState;
+ CallNumber oldRingNumber = mRingNumber;
+
+ Call foregroundCall = mCM.getActiveFgCall();
+
+ if (VDBG)
+ Log.d(TAG, " handlePreciseCallStateChange: foreground: " + foregroundCall +
+ " background: " + mCM.getFirstActiveBgCall() + " ringing: " +
+ mCM.getFirstActiveRingingCall());
+
+ mForegroundCallState = foregroundCall.getState();
+ /* if in transition, do not update */
+ if (mForegroundCallState == Call.State.DISCONNECTING)
+ {
+ Log.d(TAG, "handlePreciseCallStateChange. Call disconnecting, wait before update");
+ return;
+ }
+ else
+ mNumActive = (mForegroundCallState == Call.State.ACTIVE) ? 1 : 0;
+
+ Call ringingCall = mCM.getFirstActiveRingingCall();
+ mRingingCallState = ringingCall.getState();
+ mRingNumber = getCallNumber(connection, ringingCall);
+
+ if (mCM.getDefaultPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
+ mNumHeld = getNumHeldCdma();
+ PhoneGlobals app = PhoneGlobals.getInstance();
+ if (app.cdmaPhoneCallState != null) {
+ CdmaPhoneCallState.PhoneCallState currCdmaThreeWayCallState =
+ app.cdmaPhoneCallState.getCurrentCallState();
+ CdmaPhoneCallState.PhoneCallState prevCdmaThreeWayCallState =
+ app.cdmaPhoneCallState.getPreviousCallState();
+
+ log("CDMA call state: " + currCdmaThreeWayCallState + " prev state:" +
+ prevCdmaThreeWayCallState);
+
+ if ((mBluetoothHeadset != null) &&
+ (mCdmaThreeWayCallState != currCdmaThreeWayCallState)) {
+ // In CDMA, the network does not provide any feedback
+ // to the phone when the 2nd MO call goes through the
+ // stages of DIALING > ALERTING -> ACTIVE we fake the
+ // sequence
+ log("CDMA 3way call state change. mNumActive: " + mNumActive +
+ " mNumHeld: " + mNumHeld + " IsThreeWayCallOrigStateDialing: " +
+ app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing());
+ if ((currCdmaThreeWayCallState ==
+ CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
+ && app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing()) {
+ // Mimic dialing, put the call on hold, alerting
+ mBluetoothHeadset.phoneStateChanged(0, mNumHeld,
+ convertCallState(Call.State.IDLE, Call.State.DIALING),
+ mRingNumber.mNumber, mRingNumber.mType);
+
+ mBluetoothHeadset.phoneStateChanged(0, mNumHeld,
+ convertCallState(Call.State.IDLE, Call.State.ALERTING),
+ mRingNumber.mNumber, mRingNumber.mType);
+
+ }
+
+ // In CDMA, the network does not provide any feedback to
+ // the phone when a user merges a 3way call or swaps
+ // between two calls we need to send a CIEV response
+ // indicating that a call state got changed which should
+ // trigger a CLCC update request from the BT client.
+ if (currCdmaThreeWayCallState ==
+ CdmaPhoneCallState.PhoneCallState.CONF_CALL &&
+ prevCdmaThreeWayCallState ==
+ CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
+ log("CDMA 3way conf call. mNumActive: " + mNumActive +
+ " mNumHeld: " + mNumHeld);
+ mBluetoothHeadset.phoneStateChanged(mNumActive, mNumHeld,
+ convertCallState(Call.State.IDLE, mForegroundCallState),
+ mRingNumber.mNumber, mRingNumber.mType);
+ }
+ }
+ mCdmaThreeWayCallState = currCdmaThreeWayCallState;
+ }
+ } else {
+ mNumHeld = getNumHeldUmts();
+ }
+
+ boolean callsSwitched = false;
+ if (mCM.getDefaultPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA &&
+ mCdmaThreeWayCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
+ callsSwitched = mCdmaCallsSwapped;
+ } else {
+ Call backgroundCall = mCM.getFirstActiveBgCall();
+ callsSwitched =
+ (mNumHeld == 1 && ! (backgroundCall.getEarliestConnectTime() ==
+ mBgndEarliestConnectionTime));
+ mBgndEarliestConnectionTime = backgroundCall.getEarliestConnectTime();
+ }
+
+ if (mNumActive != oldNumActive || mNumHeld != oldNumHeld ||
+ mRingingCallState != oldRingingCallState ||
+ mForegroundCallState != oldForegroundCallState ||
+ !mRingNumber.equalTo(oldRingNumber) ||
+ callsSwitched) {
+ if (mBluetoothHeadset != null) {
+ mBluetoothHeadset.phoneStateChanged(mNumActive, mNumHeld,
+ convertCallState(mRingingCallState, mForegroundCallState),
+ mRingNumber.mNumber, mRingNumber.mType);
+ }
+ }
+ }
+
+ private void handleListCurrentCalls() {
+ Phone phone = mCM.getDefaultPhone();
+ int phoneType = phone.getPhoneType();
+
+ // TODO(BT) handle virtual call
+
+ if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+ listCurrentCallsCdma();
+ } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
+ listCurrentCallsGsm();
+ } else {
+ Log.e(TAG, "Unexpected phone type: " + phoneType);
+ }
+ // end the result
+ // when index is 0, other parameter does not matter
+ mBluetoothHeadset.clccResponse(0, 0, 0, 0, false, "", 0);
+ }
+
+ private void handleQueryPhoneState() {
+ if (mBluetoothHeadset != null) {
+ mBluetoothHeadset.phoneStateChanged(mNumActive, mNumHeld,
+ convertCallState(mRingingCallState, mForegroundCallState),
+ mRingNumber.mNumber, mRingNumber.mType);
+ }
+ }
+
+ private int getNumHeldUmts() {
+ int countHeld = 0;
+ List<Call> heldCalls = mCM.getBackgroundCalls();
+
+ for (Call call : heldCalls) {
+ if (call.getState() == Call.State.HOLDING) {
+ countHeld++;
+ }
+ }
+ return countHeld;
+ }
+
+ private int getNumHeldCdma() {
+ int numHeld = 0;
+ PhoneGlobals app = PhoneGlobals.getInstance();
+ if (app.cdmaPhoneCallState != null) {
+ CdmaPhoneCallState.PhoneCallState curr3WayCallState =
+ app.cdmaPhoneCallState.getCurrentCallState();
+ CdmaPhoneCallState.PhoneCallState prev3WayCallState =
+ app.cdmaPhoneCallState.getPreviousCallState();
+
+ log("CDMA call state: " + curr3WayCallState + " prev state:" +
+ prev3WayCallState);
+ if (curr3WayCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
+ if (prev3WayCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
+ numHeld = 0; //0: no calls held, as now *both* the caller are active
+ } else {
+ numHeld = 1; //1: held call and active call, as on answering a
+ // Call Waiting, one of the caller *is* put on hold
+ }
+ } else if (curr3WayCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
+ numHeld = 1; //1: held call and active call, as on make a 3 Way Call
+ // the first caller *is* put on hold
+ } else {
+ numHeld = 0; //0: no calls held as this is a SINGLE_ACTIVE call
+ }
+ }
+ return numHeld;
+ }
+
+ private CallNumber getCallNumber(Connection connection, Call call) {
+ String number = null;
+ int type = 128;
+ // find phone number and type
+ if (connection == null) {
+ connection = call.getEarliestConnection();
+ if (connection == null) {
+ Log.e(TAG, "Could not get a handle on Connection object for the call");
+ }
+ }
+ if (connection != null) {
+ number = connection.getAddress();
+ if (number != null) {
+ type = PhoneNumberUtils.toaFromString(number);
+ }
+ }
+ if (number == null) {
+ number = "";
+ }
+ return new CallNumber(number, type);
+ }
+
+ private class CallNumber
+ {
+ private String mNumber = null;
+ private int mType = 0;
+
+ private CallNumber(String number, int type) {
+ mNumber = number;
+ mType = type;
+ }
+
+ private boolean equalTo(CallNumber callNumber)
+ {
+ if (mType != callNumber.mType) return false;
+
+ if (mNumber != null && mNumber.compareTo(callNumber.mNumber) == 0) {
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private BluetoothProfile.ServiceListener mProfileListener =
+ new BluetoothProfile.ServiceListener() {
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ mBluetoothHeadset = (BluetoothHeadset) proxy;
+ }
+ public void onServiceDisconnected(int profile) {
+ mBluetoothHeadset = null;
+ }
+ };
+
+ private void listCurrentCallsGsm() {
+ // Collect all known connections
+ // clccConnections isindexed by CLCC index
+ Connection[] clccConnections = new Connection[GSM_MAX_CONNECTIONS];
+ LinkedList<Connection> newConnections = new LinkedList<Connection>();
+ LinkedList<Connection> connections = new LinkedList<Connection>();
+
+ Call foregroundCall = mCM.getActiveFgCall();
+ Call backgroundCall = mCM.getFirstActiveBgCall();
+ Call ringingCall = mCM.getFirstActiveRingingCall();
+
+ if (ringingCall.getState().isAlive()) {
+ connections.addAll(ringingCall.getConnections());
+ }
+ if (foregroundCall.getState().isAlive()) {
+ connections.addAll(foregroundCall.getConnections());
+ }
+ if (backgroundCall.getState().isAlive()) {
+ connections.addAll(backgroundCall.getConnections());
+ }
+
+ // Mark connections that we already known about
+ boolean clccUsed[] = new boolean[GSM_MAX_CONNECTIONS];
+ for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
+ clccUsed[i] = mClccUsed[i];
+ mClccUsed[i] = false;
+ }
+ for (Connection c : connections) {
+ boolean found = false;
+ long timestamp = c.getCreateTime();
+ for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
+ if (clccUsed[i] && timestamp == mClccTimestamps[i]) {
+ mClccUsed[i] = true;
+ found = true;
+ clccConnections[i] = c;
+ break;
+ }
+ }
+ if (!found) {
+ newConnections.add(c);
+ }
+ }
+
+ // Find a CLCC index for new connections
+ while (!newConnections.isEmpty()) {
+ // Find lowest empty index
+ int i = 0;
+ while (mClccUsed[i]) i++;
+ // Find earliest connection
+ long earliestTimestamp = newConnections.get(0).getCreateTime();
+ Connection earliestConnection = newConnections.get(0);
+ for (int j = 0; j < newConnections.size(); j++) {
+ long timestamp = newConnections.get(j).getCreateTime();
+ if (timestamp < earliestTimestamp) {
+ earliestTimestamp = timestamp;
+ earliestConnection = newConnections.get(j);
+ }
+ }
+
+ // update
+ mClccUsed[i] = true;
+ mClccTimestamps[i] = earliestTimestamp;
+ clccConnections[i] = earliestConnection;
+ newConnections.remove(earliestConnection);
+ }
+
+ // Send CLCC response to Bluetooth headset service
+ for (int i = 0; i < clccConnections.length; i++) {
+ if (mClccUsed[i]) {
+ sendClccResponseGsm(i, clccConnections[i]);
+ }
+ }
+ }
+
+ /** Convert a Connection object into a single +CLCC result */
+ private void sendClccResponseGsm(int index, Connection connection) {
+ int state = convertCallState(connection.getState());
+ boolean mpty = false;
+ Call call = connection.getCall();
+ if (call != null) {
+ mpty = call.isMultiparty();
+ }
+
+ int direction = connection.isIncoming() ? 1 : 0;
+
+ String number = connection.getAddress();
+ int type = -1;
+ if (number != null) {
+ type = PhoneNumberUtils.toaFromString(number);
+ }
+
+ mBluetoothHeadset.clccResponse(index + 1, direction, state, 0, mpty, number, type);
+ }
+
+ /** Build the +CLCC result for CDMA
+ * The complexity arises from the fact that we need to maintain the same
+ * CLCC index even as a call moves between states. */
+ private synchronized void listCurrentCallsCdma() {
+ // In CDMA at one time a user can have only two live/active connections
+ Connection[] clccConnections = new Connection[CDMA_MAX_CONNECTIONS];// indexed by CLCC index
+ Call foregroundCall = mCM.getActiveFgCall();
+ Call ringingCall = mCM.getFirstActiveRingingCall();
+
+ Call.State ringingCallState = ringingCall.getState();
+ // If the Ringing Call state is INCOMING, that means this is the very first call
+ // hence there should not be any Foreground Call
+ if (ringingCallState == Call.State.INCOMING) {
+ if (VDBG) log("Filling clccConnections[0] for INCOMING state");
+ clccConnections[0] = ringingCall.getLatestConnection();
+ } else if (foregroundCall.getState().isAlive()) {
+ // Getting Foreground Call connection based on Call state
+ if (ringingCall.isRinging()) {
+ if (VDBG) log("Filling clccConnections[0] & [1] for CALL WAITING state");
+ clccConnections[0] = foregroundCall.getEarliestConnection();
+ clccConnections[1] = ringingCall.getLatestConnection();
+ } else {
+ if (foregroundCall.getConnections().size() <= 1) {
+ // Single call scenario
+ if (VDBG) {
+ log("Filling clccConnections[0] with ForgroundCall latest connection");
+ }
+ clccConnections[0] = foregroundCall.getLatestConnection();
+ } else {
+ // Multiple Call scenario. This would be true for both
+ // CONF_CALL and THRWAY_ACTIVE state
+ if (VDBG) {
+ log("Filling clccConnections[0] & [1] with ForgroundCall connections");
+ }
+ clccConnections[0] = foregroundCall.getEarliestConnection();
+ clccConnections[1] = foregroundCall.getLatestConnection();
+ }
+ }
+ }
+
+ // Update the mCdmaIsSecondCallActive flag based on the Phone call state
+ if (PhoneGlobals.getInstance().cdmaPhoneCallState.getCurrentCallState()
+ == CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE) {
+ Message msg = mHandler.obtainMessage(CDMA_SET_SECOND_CALL_STATE, false);
+ mHandler.sendMessage(msg);
+ } else if (PhoneGlobals.getInstance().cdmaPhoneCallState.getCurrentCallState()
+ == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
+ Message msg = mHandler.obtainMessage(CDMA_SET_SECOND_CALL_STATE, true);
+ mHandler.sendMessage(msg);
+ }
+
+ // send CLCC result
+ for (int i = 0; (i < clccConnections.length) && (clccConnections[i] != null); i++) {
+ sendClccResponseCdma(i, clccConnections[i]);
+ }
+ }
+
+ /** Send ClCC results for a Connection object for CDMA phone */
+ private void sendClccResponseCdma(int index, Connection connection) {
+ int state;
+ PhoneGlobals app = PhoneGlobals.getInstance();
+ CdmaPhoneCallState.PhoneCallState currCdmaCallState =
+ app.cdmaPhoneCallState.getCurrentCallState();
+ CdmaPhoneCallState.PhoneCallState prevCdmaCallState =
+ app.cdmaPhoneCallState.getPreviousCallState();
+
+ if ((prevCdmaCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
+ && (currCdmaCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL)) {
+ // If the current state is reached after merging two calls
+ // we set the state of all the connections as ACTIVE
+ state = CALL_STATE_ACTIVE;
+ } else {
+ Call.State callState = connection.getState();
+ switch (callState) {
+ case ACTIVE:
+ // For CDMA since both the connections are set as active by FW after accepting
+ // a Call waiting or making a 3 way call, we need to set the state specifically
+ // to ACTIVE/HOLDING based on the mCdmaIsSecondCallActive flag. This way the
+ // CLCC result will allow BT devices to enable the swap or merge options
+ if (index == 0) { // For the 1st active connection
+ state = mCdmaIsSecondCallActive ? CALL_STATE_HELD : CALL_STATE_ACTIVE;
+ } else { // for the 2nd active connection
+ state = mCdmaIsSecondCallActive ? CALL_STATE_ACTIVE : CALL_STATE_HELD;
+ }
+ break;
+ case HOLDING:
+ state = CALL_STATE_HELD;
+ break;
+ case DIALING:
+ state = CALL_STATE_DIALING;
+ break;
+ case ALERTING:
+ state = CALL_STATE_ALERTING;
+ break;
+ case INCOMING:
+ state = CALL_STATE_INCOMING;
+ break;
+ case WAITING:
+ state = CALL_STATE_WAITING;
+ break;
+ default:
+ Log.e(TAG, "bad call state: " + callState);
+ return;
+ }
+ }
+
+ boolean mpty = false;
+ if (currCdmaCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
+ if (prevCdmaCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
+ // If the current state is reached after merging two calls
+ // we set the multiparty call true.
+ mpty = true;
+ } // else
+ // CALL_CONF state is not from merging two calls, but from
+ // accepting the second call. In this case first will be on
+ // hold in most cases but in some cases its already merged.
+ // However, we will follow the common case and the test case
+ // as per Bluetooth SIG PTS
+ }
+
+ int direction = connection.isIncoming() ? 1 : 0;
+
+ String number = connection.getAddress();
+ int type = -1;
+ if (number != null) {
+ type = PhoneNumberUtils.toaFromString(number);
+ } else {
+ number = "";
+ }
+
+ mBluetoothHeadset.clccResponse(index + 1, direction, state, 0, mpty, number, type);
+ }
+
+ private void handleCdmaSwapSecondCallState() {
+ if (VDBG) log("cdmaSwapSecondCallState: Toggling mCdmaIsSecondCallActive");
+ mCdmaIsSecondCallActive = !mCdmaIsSecondCallActive;
+ mCdmaCallsSwapped = true;
+ }
+
+ private void handleCdmaSetSecondCallState(boolean state) {
+ if (VDBG) log("cdmaSetSecondCallState: Setting mCdmaIsSecondCallActive to " + state);
+ mCdmaIsSecondCallActive = state;
+
+ if (!mCdmaIsSecondCallActive) {
+ mCdmaCallsSwapped = false;
+ }
+ }
+
+ private final IBluetoothHeadsetPhone.Stub mBinder = new IBluetoothHeadsetPhone.Stub() {
+ public boolean answerCall() {
+ enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+ return PhoneUtils.answerCall(mCM.getFirstActiveRingingCall());
+ }
+
+ public boolean hangupCall() {
+ enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+ if (mCM.hasActiveFgCall()) {
+ return PhoneUtils.hangupActiveCall(mCM.getActiveFgCall());
+ } else if (mCM.hasActiveRingingCall()) {
+ return PhoneUtils.hangupRingingCall(mCM.getFirstActiveRingingCall());
+ } else if (mCM.hasActiveBgCall()) {
+ return PhoneUtils.hangupHoldingCall(mCM.getFirstActiveBgCall());
+ }
+ // TODO(BT) handle virtual voice call
+ return false;
+ }
+
+ public boolean sendDtmf(int dtmf) {
+ enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+ return mCM.sendDtmf((char) dtmf);
+ }
+
+ public boolean processChld(int chld) {
+ enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+ Phone phone = mCM.getDefaultPhone();
+ int phoneType = phone.getPhoneType();
+ Call ringingCall = mCM.getFirstActiveRingingCall();
+ Call backgroundCall = mCM.getFirstActiveBgCall();
+
+ if (chld == CHLD_TYPE_RELEASEHELD) {
+ if (ringingCall.isRinging()) {
+ return PhoneUtils.hangupRingingCall(ringingCall);
+ } else {
+ return PhoneUtils.hangupHoldingCall(backgroundCall);
+ }
+ } else if (chld == CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD) {
+ if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+ if (ringingCall.isRinging()) {
+ // Hangup the active call and then answer call waiting call.
+ if (VDBG) log("CHLD:1 Callwaiting Answer call");
+ PhoneUtils.hangupRingingAndActive(phone);
+ } else {
+ // If there is no Call waiting then just hangup
+ // the active call. In CDMA this mean that the complete
+ // call session would be ended
+ if (VDBG) log("CHLD:1 Hangup Call");
+ PhoneUtils.hangup(PhoneGlobals.getInstance().mCM);
+ }
+ return true;
+ } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
+ // Hangup active call, answer held call
+ return PhoneUtils.answerAndEndActive(PhoneGlobals.getInstance().mCM, ringingCall);
+ } else {
+ Log.e(TAG, "bad phone type: " + phoneType);
+ return false;
+ }
+ } else if (chld == CHLD_TYPE_HOLDACTIVE_ACCEPTHELD) {
+ if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+ // For CDMA, the way we switch to a new incoming call is by
+ // calling PhoneUtils.answerCall(). switchAndHoldActive() won't
+ // properly update the call state within telephony.
+ // If the Phone state is already in CONF_CALL then we simply send
+ // a flash cmd by calling switchHoldingAndActive()
+ if (ringingCall.isRinging()) {
+ if (VDBG) log("CHLD:2 Callwaiting Answer call");
+ PhoneUtils.answerCall(ringingCall);
+ PhoneUtils.setMute(false);
+ // Setting the second callers state flag to TRUE (i.e. active)
+ cdmaSetSecondCallState(true);
+ return true;
+ } else if (PhoneGlobals.getInstance().cdmaPhoneCallState
+ .getCurrentCallState()
+ == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
+ if (VDBG) log("CHLD:2 Swap Calls");
+ PhoneUtils.switchHoldingAndActive(backgroundCall);
+ // Toggle the second callers active state flag
+ cdmaSwapSecondCallState();
+ return true;
+ }
+ Log.e(TAG, "CDMA fail to do hold active and accept held");
+ return false;
+ } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
+ PhoneUtils.switchHoldingAndActive(backgroundCall);
+ return true;
+ } else {
+ Log.e(TAG, "Unexpected phone type: " + phoneType);
+ return false;
+ }
+ } else if (chld == CHLD_TYPE_ADDHELDTOCONF) {
+ if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+ CdmaPhoneCallState.PhoneCallState state =
+ PhoneGlobals.getInstance().cdmaPhoneCallState.getCurrentCallState();
+ // For CDMA, we need to check if the call is in THRWAY_ACTIVE state
+ if (state == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
+ if (VDBG) log("CHLD:3 Merge Calls");
+ PhoneUtils.mergeCalls();
+ return true;
+ } else if (state == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
+ // State is CONF_CALL already and we are getting a merge call
+ // This can happen when CONF_CALL was entered from a Call Waiting
+ // TODO(BT)
+ return false;
+ }
+ Log.e(TAG, "GSG no call to add conference");
+ return false;
+ } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
+ if (mCM.hasActiveFgCall() && mCM.hasActiveBgCall()) {
+ PhoneUtils.mergeCalls();
+ return true;
+ } else {
+ Log.e(TAG, "GSG no call to merge");
+ return false;
+ }
+ } else {
+ Log.e(TAG, "Unexpected phone type: " + phoneType);
+ return false;
+ }
+ } else {
+ Log.e(TAG, "bad CHLD value: " + chld);
+ return false;
+ }
+ }
+
+ public String getNetworkOperator() {
+ enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+ return mCM.getDefaultPhone().getServiceState().getOperatorAlphaLong();
+ }
+
+ public String getSubscriberNumber() {
+ enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+ return mCM.getDefaultPhone().getLine1Number();
+ }
+
+ public boolean listCurrentCalls() {
+ enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+ Message msg = Message.obtain(mHandler, LIST_CURRENT_CALLS);
+ mHandler.sendMessage(msg);
+ return true;
+ }
+
+ public boolean queryPhoneState() {
+ enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+ Message msg = Message.obtain(mHandler, QUERY_PHONE_STATE);
+ mHandler.sendMessage(msg);
+ return true;
+ }
+
+ public void updateBtHandsfreeAfterRadioTechnologyChange() {
+ enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+ if (VDBG) Log.d(TAG, "updateBtHandsfreeAfterRadioTechnologyChange...");
+ updateBtPhoneStateAfterRadioTechnologyChange();
+ }
+
+ public void cdmaSwapSecondCallState() {
+ enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+ Message msg = Message.obtain(mHandler, CDMA_SWAP_SECOND_CALL_STATE);
+ mHandler.sendMessage(msg);
+ }
+
+ public void cdmaSetSecondCallState(boolean state) {
+ enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+ Message msg = mHandler.obtainMessage(CDMA_SET_SECOND_CALL_STATE, state);
+ mHandler.sendMessage(msg);
+ }
+ };
+
+ // match up with bthf_call_state_t of bt_hf.h
+ final static int CALL_STATE_ACTIVE = 0;
+ final static int CALL_STATE_HELD = 1;
+ final static int CALL_STATE_DIALING = 2;
+ final static int CALL_STATE_ALERTING = 3;
+ final static int CALL_STATE_INCOMING = 4;
+ final static int CALL_STATE_WAITING = 5;
+ final static int CALL_STATE_IDLE = 6;
+
+ // match up with bthf_chld_type_t of bt_hf.h
+ final static int CHLD_TYPE_RELEASEHELD = 0;
+ final static int CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD = 1;
+ final static int CHLD_TYPE_HOLDACTIVE_ACCEPTHELD = 2;
+ final static int CHLD_TYPE_ADDHELDTOCONF = 3;
+
+ /* Convert telephony phone call state into hf hal call state */
+ static int convertCallState(Call.State ringingState, Call.State foregroundState) {
+ if ((ringingState == Call.State.INCOMING) ||
+ (ringingState == Call.State.WAITING) )
+ return CALL_STATE_INCOMING;
+ else if (foregroundState == Call.State.DIALING)
+ return CALL_STATE_DIALING;
+ else if (foregroundState == Call.State.ALERTING)
+ return CALL_STATE_ALERTING;
+ else
+ return CALL_STATE_IDLE;
+ }
+
+ static int convertCallState(Call.State callState) {
+ switch (callState) {
+ case IDLE:
+ case DISCONNECTED:
+ case DISCONNECTING:
+ return CALL_STATE_IDLE;
+ case ACTIVE:
+ return CALL_STATE_ACTIVE;
+ case HOLDING:
+ return CALL_STATE_HELD;
+ case DIALING:
+ return CALL_STATE_DIALING;
+ case ALERTING:
+ return CALL_STATE_ALERTING;
+ case INCOMING:
+ return CALL_STATE_INCOMING;
+ case WAITING:
+ return CALL_STATE_WAITING;
+ default:
+ Log.e(TAG, "bad call state: " + callState);
+ return CALL_STATE_IDLE;
+ }
+ }
+
+ private static void log(String msg) {
+ Log.d(TAG, msg);
+ }
+}