| /* |
| * 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; |
| |
| private boolean mRoam = false; |
| |
| // 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);; |
| mRoam = false; |
| |
| updateServiceState(mCM.getDefaultPhone().getServiceState()); |
| handlePreciseCallStateChange(null); |
| |
| if(VDBG) Log.d(TAG, "registerForServiceStateChanged"); |
| // register for updates |
| // Use the service state of default phone as BT service state to |
| // avoid situation such as no cell or wifi connection but still |
| // reporting in service (since SipPhone always reports in service). |
| mCM.getDefaultPhone().registerForServiceStateChanged(mHandler, |
| SERVICE_STATE_CHANGED, null); |
| 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 SERVICE_STATE_CHANGED = 1; |
| private static final int PRECISE_CALL_STATE_CHANGED = 2; |
| private static final int PHONE_CDMA_CALL_WAITING = 3; |
| private static final int LIST_CURRENT_CALLS = 4; |
| private static final int QUERY_PHONE_STATE = 5; |
| private static final int CDMA_SWAP_SECOND_CALL_STATE = 6; |
| private static final int CDMA_SET_SECOND_CALL_STATE = 7; |
| |
| private Handler mHandler = new Handler() { |
| @Override |
| public void handleMessage(Message msg) { |
| if (VDBG) Log.d(TAG, "handleMessage: " + msg.what); |
| switch(msg.what) { |
| case SERVICE_STATE_CHANGED: |
| ServiceState state = (ServiceState) ((AsyncResult) msg.obj).result; |
| updateServiceState(state); |
| break; |
| 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.getDefaultPhone().unregisterForServiceStateChanged(mHandler); |
| mCM.unregisterForPreciseCallStateChanged(mHandler); |
| mCM.unregisterForCallWaiting(mHandler); |
| |
| //Register all events new to the new active phone |
| mCM.getDefaultPhone().registerForServiceStateChanged(mHandler, |
| SERVICE_STATE_CHANGED, null); |
| mCM.registerForPreciseCallStateChanged(mHandler, |
| PRECISE_CALL_STATE_CHANGED, null); |
| mCM.registerForCallWaiting(mHandler, |
| PHONE_CDMA_CALL_WAITING, null); |
| } |
| |
| private void updateServiceState(ServiceState state) { |
| boolean roam = state.getRoaming(); |
| |
| if (roam != mRoam) { |
| mRoam = roam; |
| if (mBluetoothHeadset != null) { |
| mBluetoothHeadset.roamChanged(roam); |
| } |
| } |
| } |
| |
| 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); |
| } |
| } |