www.pudn.com > WINCEOS.zip > handler.cpp


// 
// Copyright (c) Microsoft Corporation.  All rights reserved. 
// 
// 
// This source code is licensed under Microsoft Shared Source License 
// Version 1.0 for Windows CE. 
// For a copy of the license visit http://go.microsoft.com/fwlink/?LinkId=3223. 
// 
 
#include  
#include  
#include  
 
#include "btagpriv.h" 
#include "btagnetwork.h" 
 
#ifndef PREFAST_ASSERT 
#define PREFAST_ASSERT(x) ASSERT(x) 
#endif 
 
 
#define AT_OK       "\r\nOK\r\n" 
#define AT_ERROR    "\r\nERROR\r\n" 
#define AT_VGS      "\r\n+VGS=%d\r\n" 
#define AT_VGM      "\r\n+VGM=%d\r\n" 
#define AT_RING     "\r\nRING\r\n" 
 
 
void PhoneExtServiceCallback(BOOL fHaveService); 
 
BOOL IsHandsfreeUUID(const GUID* pGuid) 
{ 
    ASSERT(pGuid); 
    return !memcmp(pGuid, &HandsfreeServiceClass_UUID, sizeof(*pGuid)); 
} 
 
CAGEngine::CAGEngine(void) 
{ 
} 
 
 
// This method initializes the AG engine 
DWORD CAGEngine::Init(CATParser* pParser) 
{ 
    DWORD dwRetVal = ERROR_SUCCESS; 
 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: AG Engine is being initialized.\n")); 
 
    Lock(); 
 
    ASSERT(pParser); 
     
    m_pParser = pParser; 
    m_fExpectHeadsetButton = FALSE; 
    m_fAudioConnect = FALSE; 
    m_hSco = 0; 
    m_hUIThread = NULL; 
    m_fPhoneExtInited = FALSE; 
    m_fNetworkInited = FALSE; 
    m_wszCLI[0] = 0; 
    m_fCancelCloseConnection = FALSE; 
    m_CloseCookie = NULL; 
    m_SniffCookie = NULL; 
 
    memset(&m_AGProps, 0, sizeof(AG_PROPS)); 
 
    m_AGProps.fPCMMode = TRUE; 
    m_AGProps.usMicVolume = 8; 
    m_AGProps.usSpeakerVolume = 8; 
    m_AGProps.fAuth = TRUE; 
    m_AGProps.fEncrypt = TRUE; 
    m_AGProps.usHFCapability = DEFAULT_AG_CAPABILITY; 
    m_AGProps.usPageTimeout = DEFAULT_PAGE_TIMEOUT; 
    m_AGProps.ulSniffDelay = DEFAULT_SNIFF_DELAY; 
    m_AGProps.usSniffMax = DEFAULT_SNIFF_MAX; 
    m_AGProps.usSniffMin = DEFAULT_SNIFF_MIN;     
    m_AGProps.usSniffAttempt = DEFAULT_SNIFF_ATTEMPT; 
    m_AGProps.usSniffTimeout = DEFAULT_SNIFF_TO; 
 
    m_pTP = new SVSThreadPool(2); 
    if (! m_pTP) { 
        Unlock(); 
        return ERROR_OUTOFMEMORY; 
    } 
 
    DWORD dwErr = BthAGNetworkInit(g_hInstance); 
    if (ERROR_SUCCESS != dwErr) { 
        // This is not a critical failure, continue to load service. 
        DEBUGMSG(ZONE_WARN, (L"BTAGSVC: Error initializing network module: %d.\n", dwErr));         
    } 
    else { 
        m_fNetworkInited = TRUE; 
    }     
 
    dwErr = BthAGPhoneExtInit(); 
    if (ERROR_SUCCESS != dwErr) { 
        // This is not a critical failure, continue to load service. 
        DEBUGMSG(ZONE_WARN, (L"BTAGSVC: Error initializing PhoneExt module: %d.\n", dwErr));         
    } 
    else { 
        m_fPhoneExtInited = TRUE; 
    } 
 
    Unlock(); 
     
    if (ERROR_SUCCESS != BthAGSetServiceCallback(PhoneExtServiceCallback)) { 
        DEBUGMSG(ZONE_WARN, (L"BTAGSVC: Error setting service callback in PhoneExt.\n")); 
    } 
 
    return dwRetVal; 
} 
 
 
// This method deinitializes the AG engine 
void CAGEngine::Deinit(void) 
{ 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: AG Engine is being de-initialized.\n")); 
 
    Lock(); 
 
    if (m_pParser) { 
        CloseAudioChannel(); 
        CloseControlChannel(); 
 
        if (m_fPhoneExtInited) { 
            BthAGPhoneExtDeinit(); 
        } 
 
        if (m_fNetworkInited) { 
            BthAGNetworkDeinit(); 
        } 
 
        delete m_pTP; 
        m_pTP = NULL; 
 
        memset(&m_AGProps, 0, sizeof(AG_PROPS));     
        m_pParser = NULL; 
 
        while (GetRefCount() > 1) { 
            DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Sleeping while AGEngine ref count is still active.\n")); 
            Unlock(); 
            Sleep(500); 
            Lock(); 
        } 
    } 
     
    Unlock();     
} 
 
 
// 
// This function gets the list of HF devices from the registry 
// 
DWORD GetBTAddrList(AG_DEVICE* pDevices, const USHORT usNumDevices) 
{ 
    DWORD dwRetVal = ERROR_SUCCESS; 
    HKEY hk = NULL; 
    int dwIdx = 0; 
    WCHAR szName[MAX_PATH]; 
    DWORD cchName = ARRAY_SIZE(szName); 
 
    ASSERT(pDevices && usNumDevices); 
 
    // Clear the current list 
    memset(pDevices, 0, usNumDevices*sizeof(pDevices[0])); 
 
    dwRetVal = RegOpenKeyEx(HKEY_LOCAL_MACHINE, RK_AUDIO_GATEWAY_DEVICES, 0, 0, &hk); 
    if (ERROR_SUCCESS != dwRetVal) { 
        DEBUGMSG(ZONE_WARN, (L"BTAGSVC: Could not open registry key for BT Addr: %d.\n", dwRetVal));         
        goto exit; 
    } 
 
    // Enumerate devices in registry     
    while (ERROR_SUCCESS == RegEnumKeyEx(hk, dwIdx, szName, &cchName, NULL, NULL, NULL, NULL)) { 
        HKEY hkAddr; 
        BT_ADDR btAddr = 0; 
        GUID service; 
        DWORD cdwSize; 
         
        DWORD dwErr = RegOpenKeyEx(hk, szName, 0, 0, &hkAddr); 
        if (ERROR_SUCCESS != dwErr) { 
            DEBUGMSG(ZONE_WARN, (L"BTAGSVC: Could not open registry key for BT Addr: %d.\n", dwRetVal));         
            break; 
        } 
         
        cdwSize = sizeof(btAddr); 
        dwErr = RegQueryValueEx(hkAddr, L"Address", NULL, NULL, (PBYTE)&btAddr, &cdwSize); 
        if (dwErr != ERROR_SUCCESS) { 
            DEBUGMSG(ZONE_WARN, (L"BTAGSVC: Could not query registry value for BT Addr: %d.\n", dwErr));         
            RegCloseKey(hkAddr); 
            break; 
        } 
 
        cdwSize = sizeof(service); 
        dwErr = RegQueryValueEx(hkAddr, L"Service", NULL, NULL, (PBYTE)&service, &cdwSize); 
        if (dwErr != ERROR_SUCCESS) { 
            DEBUGMSG(ZONE_WARN, (L"BTAGSVC: Could not query registry value for service: %d.\n", dwErr));         
            RegCloseKey(hkAddr); 
            break; 
        }         
 
        RegCloseKey(hkAddr); 
 
        WCHAR* p; 
        int iDeviceIdx = wcstol(szName, &p, 10); 
        if (btAddr != 0 && ((iDeviceIdx >= 1) || (iDeviceIdx <= usNumDevices))) { 
            DEBUGMSG(ZONE_OUTPUT, (L"BTAGSVC: Setting btAddr at index %d to %04x%08x.\n", iDeviceIdx, GET_NAP(btAddr), GET_SAP(btAddr)));  
            pDevices[iDeviceIdx-1].bta = btAddr; 
            memcpy(&pDevices[iDeviceIdx-1].service, &service, sizeof(GUID)); 
        } 
 
        cchName = ARRAY_SIZE(szName); 
        dwIdx++;         
    }     
 
    RegCloseKey(hk); 
 
exit: 
     
    return dwRetVal; 
} 
 
 
BOOL CAGEngine::FindBTAddrInList(const BT_ADDR btaSearch) 
{ 
    BOOL fRetVal = FALSE; 
 
    Lock(); 
 
    GetBTAddrList(m_AGProps.DeviceList, ARRAY_SIZE(m_AGProps.DeviceList)); // Update from registry 
 
    // Loop through list until we find the address 
    int i = 0; 
    while ((i < ARRAY_SIZE(m_AGProps.DeviceList)) && (btaSearch != m_AGProps.DeviceList[i].bta)) { 
        i++; 
    } 
 
    if (i < ARRAY_SIZE(m_AGProps.DeviceList)) { 
        fRetVal = TRUE; 
    } 
 
    Unlock(); 
     
    return fRetVal; 
} 
 
 
 
// This method writes the address list back to the registry 
// sets the parameter btAddrDefault at the top of the list 
// and as the active device. 
DWORD CAGEngine::SetBTAddrList(BT_ADDR btAddrDefault, BOOL fHfSupport) 
{ 
    DWORD       dwRetVal    = ERROR_SUCCESS; 
    DWORD       dwDis       = 0; 
    HKEY        hk          = NULL; 
 
    Lock(); 
 
    GetBTAddrList(m_AGProps.DeviceList, ARRAY_SIZE(m_AGProps.DeviceList)); // Update from registry 
 
    m_AGProps.btAddrClient = btAddrDefault; 
 
    // Loop through list until we find the address 
    int i = 0; 
    while ((i < ARRAY_SIZE(m_AGProps.DeviceList)) && (btAddrDefault != m_AGProps.DeviceList[i].bta)) { 
        i++; 
    } 
     
    if (i > 0) { 
        GUID service; 
         
        if (i == ARRAY_SIZE(m_AGProps.DeviceList)) { 
            // This is a very unlikely scenario.  We get a connection request after pairing 
            // with the device but before SDP is done. 
            service = fHfSupport ? HandsfreeServiceClass_UUID : HeadsetServiceClass_UUID; 
        } 
        else { 
            service = m_AGProps.DeviceList[i].service; 
        } 
 
        memmove(&m_AGProps.DeviceList[1], m_AGProps.DeviceList, i*sizeof(AG_DEVICE)); 
         
        m_AGProps.DeviceList[0].bta = btAddrDefault; 
        m_AGProps.DeviceList[0].service = service; 
    } 
 
    // Write the list back to the registry 
    dwRetVal = RegCreateKeyEx(HKEY_LOCAL_MACHINE, RK_AUDIO_GATEWAY_DEVICES, 0, NULL, 0, NULL, NULL, &hk, &dwDis); 
    if (ERROR_SUCCESS == dwRetVal) { 
        int dwIdx = 0; 
        DWORD cbName = MAX_PATH; 
 
        while ((dwIdx < ARRAY_SIZE(m_AGProps.DeviceList)) && m_AGProps.DeviceList[dwIdx].bta != 0) { 
            HKEY hkAddr; 
            WCHAR wszIdx[10];             
            _itow(dwIdx+1, wszIdx, 10); 
 
            dwDis = 0; 
            DWORD dwErr = RegCreateKeyEx(hk, wszIdx, 0, NULL, 0, NULL, NULL, &hkAddr, &dwDis); 
            if (ERROR_SUCCESS != dwErr) { 
                DEBUGMSG(ZONE_WARN, (L"BTAGSVC: Could not open registry key for BT Addr: %d.\n", dwRetVal));         
                break; 
            } 
                 
            dwErr = RegSetValueEx(hkAddr, L"Address", NULL, REG_BINARY, (PBYTE)&m_AGProps.DeviceList[dwIdx].bta, sizeof(BT_ADDR)); 
            if (dwErr != ERROR_SUCCESS) { 
                RegCloseKey(hkAddr); 
                DEBUGMSG(ZONE_WARN, (L"BTAGSVC: Could not set registry value for BT Addr: %d.\n", dwErr));         
                break; 
            } 
 
            dwErr = RegSetValueEx(hkAddr, L"Service", NULL, REG_BINARY, (PBYTE)&m_AGProps.DeviceList[dwIdx].service, sizeof(GUID)); 
            if (dwErr != ERROR_SUCCESS) { 
                RegCloseKey(hkAddr); 
                DEBUGMSG(ZONE_WARN, (L"BTAGSVC: Could not set registry value for service: %d.\n", dwErr));         
                break; 
            } 
 
            DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Saving to registry btAddr %04x%08x at index %d.\n", GET_NAP(m_AGProps.DeviceList[dwIdx].bta), GET_SAP(m_AGProps.DeviceList[dwIdx].bta), dwIdx+1)); 
 
            RegCloseKey(hkAddr); 
             
            dwIdx++;         
        } 
 
        RegCloseKey (hk); 
    } 
 
    Unlock(); 
 
    return dwRetVal; 
} 
 
 
// This method notifies the AG Engine that a connection has been made with a peer 
DWORD CAGEngine::NotifyConnect(SOCKET sockClient, BOOL fHFSupport) 
{ 
    DWORD dwRetVal = ERROR_SUCCESS; 
     
    Lock(); 
     
    if (m_AGProps.state >= AG_STATE_CONNECTED) { 
        dwRetVal = ERROR_ALREADY_INITIALIZED; 
        goto exit; 
    } 
 
    m_AGProps.state = AG_STATE_CONNECTING; 
 
    if (m_pParser) { 
        dwRetVal = m_pParser->Start(this, sockClient);         
    } 
    else { 
        dwRetVal = ERROR_NOT_READY; 
    } 
    if (ERROR_SUCCESS != dwRetVal) { 
        DEBUGMSG(ZONE_ERROR, (L"BTAGSVC: Error starting parser module: %d\n", dwRetVal));  
        goto exit; 
    } 
 
    ASSERT(INVALID_SOCKET != sockClient); 
     
    m_sockClient = sockClient; 
 
    AddRef(); 
    Unlock(); 
     
    BthAGPhoneExtEvent(AG_PHONE_EVENT_BT_CTRL, 1, NULL); 
 
    Lock(); 
    DelRef();    
 
    m_fAudioConnect = FALSE; 
 
    m_AGProps.fHandsfree = fHFSupport;     
     
    if (!m_AGProps.fHandsfree) { 
        // For headset devices, connect audio after we have made 
        // an RFCOMM connection. 
        m_fExpectHeadsetButton = TRUE; 
        m_AGProps.state = AG_STATE_CONNECTED; 
        dwRetVal = OpenAudioChannel(); 
    } 
    
exit: 
    if (ERROR_SUCCESS != dwRetVal) { 
        CloseControlChannel(); 
    } 
     
    Unlock(); 
    return dwRetVal; 
} 
 
 
// This method is called when a connection event occurs 
void CAGEngine::ConnectionEvent(void) 
{ 
    BASEBAND_CONNECTION connections[5]; 
    int cConnectionsIn = 5; 
    int cConnectionsOut = 0; 
                 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: ConnectionEvent.\n")); 
 
    // 
    // Ensure SCO is in the correct state 
    // 
     
    Lock(); 
 
    BASEBAND_CONNECTION* pConnections = connections; 
    int iErr; 
    do { 
        if (cConnectionsOut > cConnectionsIn) { 
            if (pConnections != connections) 
                delete[] pConnections; 
            pConnections = new BASEBAND_CONNECTION[cConnectionsOut]; 
            if (! pConnections) {                             
                break; 
            } 
            cConnectionsIn = cConnectionsOut; 
        } 
        iErr = BthGetBasebandConnections(cConnectionsIn, pConnections, &cConnectionsOut);                     
    } while (ERROR_INSUFFICIENT_BUFFER == iErr); 
 
    if (ERROR_SUCCESS == iErr) {    
        BOOL fScoPresent = FALSE; 
        
        for (int i = 0; i < cConnectionsOut; i++) { 
            if ((pConnections[i].baAddress == m_AGProps.btAddrClient) &&  
            (0 == pConnections[i].fLinkType) &&  
            (m_AGProps.state >= AG_STATE_CONNECTED) 
            ) { 
                // SCO link with same address as ACL connection 
                NotifySCOConnect(pConnections[i].hConnection); 
                fScoPresent = TRUE; 
                break; 
            } 
        } 
 
        if (! fScoPresent) { 
            ClearSCO(); 
        } 
    } 
 
    if (pConnections != connections) 
        delete[] pConnections;     
 
    Unlock();     
} 
 
 
// This method notifies the AG Engine that a SCO connection has been made with a peer 
DWORD CAGEngine::NotifySCOConnect(unsigned short handle) 
{ 
    DWORD dwRetVal = ERROR_SUCCESS; 
 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: ++NotifySCOConnect.\n")); 
     
    ASSERT(IsLocked()); 
     
    if (m_AGProps.state >= AG_STATE_AUDIO_UP) { 
        DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: NotifySCOConnect - Audio connection already opened.\n")); 
        m_AGProps.state = AG_STATE_AUDIO_UP; 
        dwRetVal = ERROR_ALREADY_INITIALIZED; 
        goto exit; 
    } 
 
    if (m_AGProps.state < AG_STATE_CONNECTED) { 
        DEBUGMSG(ZONE_WARN, (L"BTAGSVC: NotifySCOConnect - Cannot open audio connection until service-level connection has been made.\n")); 
        dwRetVal = ERROR_NOT_READY; 
        goto exit;         
    } 
 
    // TODO: See if we have a built-in audio driver and do switching. 
     
    waveOutMessage(0, WODM_BT_SCO_AUDIO_CONTROL, 0, TRUE); 
 
    if (m_AGProps.fPCMMode) { 
        m_hSco = handle; 
    } 
 
    m_AGProps.state = AG_STATE_AUDIO_UP; 
 
    AddRef(); 
    Unlock(); 
     
    BthAGPhoneExtEvent(AG_PHONE_EVENT_BT_AUDIO, 1, NULL); 
 
    Lock(); 
    DelRef(); 
 
exit: 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: --NotifySCOConnect.\n")); 
     
    return dwRetVal; 
} 
 
 
// This method informs the AG that no SCO connections to the device are present.  We need to 
// make sure that audio state is no longer up. 
void CAGEngine::ClearSCO(void) 
{ 
    BOOL fClearSCO = FALSE; 
     
    ASSERT(IsLocked()); 
     
    if (AG_STATE_AUDIO_UP == m_AGProps.state) { 
        DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: SCO connection was closed.\n")); 
        m_AGProps.state = AG_STATE_CONNECTED; 
        fClearSCO = TRUE; 
    } 
    else if (AG_STATE_RINGING_AUDIO_UP == m_AGProps.state) { 
        DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: SCO connection was closed.\n")); 
        m_AGProps.state = AG_STATE_RINGING; 
        fClearSCO = TRUE; 
    } 
 
    if (fClearSCO) { 
        waveOutMessage(0, WODM_BT_SCO_AUDIO_CONTROL, 0, FALSE); 
 
        // TODO: See if we have a built-in audio driver and do switching. 
         
        m_hSco = 0; 
 
        AddRef(); 
        Unlock(); 
 
        BthAGPhoneExtEvent(AG_PHONE_EVENT_BT_AUDIO, 0, NULL); 
 
        Lock(); 
        DelRef();   
 
        // If we have not already scheduled sniff thread and sniff mode is enabled and we 
        // are connected to a HF device and are persisting HF connections, schedule the 
        // sniff thread. 
        if ((! m_SniffCookie) && m_AGProps.ulSniffDelay && m_AGProps.fHandsfree && !m_AGProps.fPowerSave) { 
            m_SniffCookie = m_pTP->ScheduleEvent(EnterSniffModeThread, (LPVOID)this, m_AGProps.ulSniffDelay);         
        } 
    } 
} 
 
 
// This method instructs the AG Engine to open a connection 
DWORD CAGEngine::OpenAGConnection(BOOL fOpenAudio, BOOL fFirstOnly) 
{ 
    DWORD dwRetVal = ERROR_SUCCESS; 
 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Opening AG connection.\n")); 
 
    Lock(); 
 
    m_fAudioConnect = fOpenAudio; 
    dwRetVal = OpenControlChannel(fFirstOnly); 
     
    Unlock(); 
     
    return dwRetVal; 
} 
 
 
// This private method opens control connection to peer 
DWORD CAGEngine::OpenControlChannel(BOOL fFirstOnly) 
{ 
    DWORD           dwRetVal = ERROR_SUCCESS; 
    SOCKADDR_BTH    sa; 
    DWORD           dwDeviceIdx = 0; 
    USHORT          usPageTimeout; 
 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Opening control connection.\n")); 
 
    ASSERT(IsLocked()); 
     
    if (m_AGProps.state >= AG_STATE_CONNECTED) { 
        // Make sure we are not timing out the connection 
        m_pTP->UnScheduleEvent(m_CloseCookie); 
        m_CloseCookie = NULL; 
        m_fCancelCloseConnection = FALSE; 
         
        if (m_fAudioConnect) { 
            DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Already connected, just opening audio connection.\n"));  
            m_fAudioConnect = FALSE; 
            dwRetVal = OpenAudioChannel(); 
        } 
        else { 
            DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: OpenControlConnection returning ERROR_ALREADY_INITIALIZED\n"));  
            dwRetVal = ERROR_ALREADY_INITIALIZED; 
        } 
         
        goto exit; 
    } 
 
    usPageTimeout = m_AGProps.usPageTimeout; 
    if (fFirstOnly) { 
        // If we are only connecting to the pri-1 device, let's allow 
        // for a longer time out. 
        usPageTimeout *= 2; 
    } 
 
    (void) BthWritePageTimeout(usPageTimeout); // if this fails, we continue anyway     
 
    m_sockClient = socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM); 
    if (INVALID_SOCKET == m_sockClient) { 
        dwRetVal = GetLastError(); 
        DEBUGMSG(ZONE_ERROR, (L"BTAGSVC: Error opening socket: %d\n", dwRetVal));  
        goto exit; 
    } 
 
    (void) GetBTAddrList(m_AGProps.DeviceList, ARRAY_SIZE(m_AGProps.DeviceList)); // if this fails, we continue anyway 
 
    if (m_AGProps.DeviceList[0].bta == 0) { 
        DEBUGMSG(ZONE_WARN, (L"BTAGSVC: Tried to open a connection but no device has been paired yet.\n")); 
        dwRetVal = ERROR_NOT_READY; 
        goto exit; 
    } 
 
    while (dwDeviceIdx < ARRAY_SIZE(m_AGProps.DeviceList)) { 
        if (m_AGProps.DeviceList[dwDeviceIdx].bta == 0) { 
            DEBUGMSG(ZONE_WARN, (L"BTAGSVC: Device with index %d does not exist, failed to connect.\n", dwDeviceIdx)); 
            dwRetVal = ERROR_NOT_READY; 
            goto exit; 
        } 
 
        BOOL fHS = m_AGProps.fNoHandsfree || !IsHandsfreeUUID(&(m_AGProps.DeviceList[dwDeviceIdx].service)); 
         
        memset(&sa, 0, sizeof(sa)); 
        sa.addressFamily = AF_BTH; 
        sa.btAddr = m_AGProps.DeviceList[dwDeviceIdx].bta; 
 
        if (fHS) { 
            // Try to connect to a headset 
            DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: OpenControlConnection - Attempting to connect to headset device at index %d.\n", dwDeviceIdx)); 
            sa.serviceClassId = HeadsetServiceClass_UUID; 
        } 
        else { 
            // Try to connect to a hands-free 
            DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: OpenControlConnection - Attempting to connect to HF device at index %d.\n", dwDeviceIdx)); 
            sa.serviceClassId = HandsfreeServiceClass_UUID; 
        }         
         
        if (SOCKET_ERROR == connect(m_sockClient, (SOCKADDR *)&sa, sizeof(sa))) { 
            if (fFirstOnly) { 
                dwRetVal = ERROR_NOT_CONNECTED; 
                goto exit; 
            } 
            else { 
                dwDeviceIdx++; 
                continue; 
            } 
        } 
 
        if (fHS) { 
            m_AGProps.fHandsfree = FALSE; 
            DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Connected to a headset device.\n")); 
        } 
        else { 
            m_AGProps.fHandsfree = TRUE; 
            DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Connected to a hands-free device.\n")); 
        } 
   
        // If we get to here we are connected 
        SetBTAddrList(m_AGProps.DeviceList[dwDeviceIdx].bta, m_AGProps.fHandsfree); 
        break; 
    } 
 
    if (dwDeviceIdx == ARRAY_SIZE(m_AGProps.DeviceList)) { 
        DEBUGMSG(ZONE_ERROR, (L"BTAGSVC: Error: Could not connect to any devices.\n")); 
        dwRetVal = ERROR_NOT_CONNECTED; 
        goto exit; 
    } 
 
    if (m_AGProps.fHandsfree) { 
        m_AGProps.state = AG_STATE_CONNECTING; 
    } 
    else { 
        m_fExpectHeadsetButton = FALSE; 
        m_AGProps.state = AG_STATE_CONNECTED; 
    } 
 
 
    if (m_pParser) { 
        dwRetVal = m_pParser->Start(this, m_sockClient); 
    } 
    else { 
        dwRetVal = ERROR_NOT_READY; 
    } 
    if (ERROR_SUCCESS != dwRetVal) { 
        DEBUGMSG(ZONE_ERROR, (L"BTAGSVC: Error starting parser module: %d\n", dwRetVal)); 
        goto exit; 
    } 
 
    AddRef(); 
    Unlock(); 
 
    BthAGPhoneExtEvent(AG_PHONE_EVENT_BT_CTRL, 1, NULL); 
 
    Lock(); 
    DelRef(); 
 
    if (m_fAudioConnect && !m_AGProps.fHandsfree) { 
        // Open audio connection 
        m_fAudioConnect = FALSE; 
        dwRetVal = OpenAudioChannel(); 
    } 
 
exit: 
    if ((ERROR_SUCCESS != dwRetVal) && (ERROR_ALREADY_INITIALIZED != dwRetVal)) { 
        CloseControlChannel(); 
    } 
         
    return dwRetVal; 
} 
 
 
// This method closes the connection to the peer device 
void CAGEngine::CloseAGConnection(BOOL fCloseControl) 
{ 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Closing AG connection.\n")); 
     
    Lock(); 
     
    if (fCloseControl || !m_AGProps.fHandsfree) { 
        CloseControlChannel(); 
    } 
    else { 
        CloseAudioChannel(); 
    } 
     
    Unlock(); 
} 
 
 
// This private method closes the control connection to the peer 
void CAGEngine::CloseControlChannel(void)  
{ 
    ASSERT(IsLocked()); 
 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: ++CloseControlChannel\n")); 
 
    // Make sure audio connection is closed and we are unparked 
    CloseAudioChannel(); 
    EnsureActiveBaseband(); 
 
    AddRef(); 
    Unlock(); 
 
    if (m_pParser) { 
        m_pParser->Stop(); 
    } 
     
    Lock(); 
    DelRef(); 
 
    m_sockClient = INVALID_SOCKET;  // parser will close the socket 
 
    m_AGProps.state = AG_STATE_DISCONNECTED; 
    m_AGProps.usCallType = AG_CALL_TYPE_NONE; 
    m_AGProps.fHandsfree = FALSE; 
    m_AGProps.fNotifyCallWait = FALSE; 
    m_AGProps.fNotifyCLI = FALSE; 
    m_AGProps.fIndicatorUpdates = FALSE; 
    m_AGProps.btAddrClient = 0; 
 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: --CloseControlChannel\n")); 
} 
 
 
void CAGEngine::ServiceConnectionUp() 
{ 
    // 
    // At this point, HF is initialized 
    // 
 
    ASSERT(IsLocked()); 
 
    if (AG_STATE_CONNECTING == m_AGProps.state) {         
        // If we are still in connecting state, we are now connected. 
        m_AGProps.state = AG_STATE_CONNECTED; 
 
        if (m_AGProps.fIndicatorUpdates) { 
            CHAR szCommand[MAX_SEND_BUF]; 
            int cbCommand; 
 
            if (m_AGProps.fHaveService) { 
                SendATCommand("\r\n+CIEV:1,1\r\n", 13); 
            } 
 
            if (m_AGProps.fInCall) { 
                SendATCommand("\r\n+CIEV:2,1\r\n", 13); 
            } 
 
            cbCommand = _snprintf(szCommand, MAX_SEND_BUF-1, "\r\n+CIEV:3,%d\r\n", m_AGProps.usCallSetup); 
            szCommand[MAX_SEND_BUF-1]='\0'; 
            if (cbCommand > 0) { 
                SendATCommand(szCommand, cbCommand); 
            } 
        } 
 
        if (m_fAudioConnect) { 
            OpenAudioChannel(); 
            m_fAudioConnect = FALSE; 
        } 
 
        if (m_AGProps.usHFCapability & AG_CAP_INBAND_RING) { 
            if (m_AGProps.state >= AG_STATE_AUDIO_UP) { 
                SendATCommand("\r\n+BSIR:1\r\n", 11); // In-band ring tone on 
            } 
            else { 
                SendATCommand("\r\n+BSIR:0\r\n", 11); // In-band ring tone off 
            } 
        } 
    } 
} 
 
// This method gets AG properties 
void CAGEngine::GetAGProps(PAG_PROPS pAGProps) 
{ 
    PREFAST_ASSERT(pAGProps); 
 
    Lock(); 
    *pAGProps = m_AGProps; 
    Unlock(); 
} 
 
 
// This method sets AG properties 
void CAGEngine::SetAGProps(PAG_PROPS pAGProps) 
{ 
    ASSERT((pAGProps->usSpeakerVolume >= 0) && (pAGProps->usSpeakerVolume <= 15)); 
    ASSERT((pAGProps->usMicVolume >= 0) && (pAGProps->usMicVolume <= 15)); 
 
    Lock(); 
    m_AGProps = *pAGProps; 
    Unlock(); 
}     
 
 
// This private method opens the audio connection to the peer 
DWORD CAGEngine::OpenAudioChannel(void) 
{ 
    DWORD dwRetVal = ERROR_SUCCESS; 
 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Opening audio connection.\n")); 
 
    ASSERT(IsLocked()); 
     
    if (m_AGProps.state >= AG_STATE_AUDIO_UP) { 
        DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Audio connection already opened.\n")); 
        m_AGProps.state = AG_STATE_AUDIO_UP; 
        dwRetVal = ERROR_ALREADY_INITIALIZED; 
        goto exit; 
    } 
 
    if (m_AGProps.state < AG_STATE_CONNECTED) { 
        DEBUGMSG(ZONE_WARN, (L"BTAGSVC: Cannot open audio connection until service-level connection has been made.\n")); 
        dwRetVal = ERROR_NOT_READY; 
        goto exit;         
    } 
 
    EnsureActiveBaseband(); 
 
    if (m_AGProps.fPCMMode) { 
        ASSERT(m_hSco == 0); 
         
        dwRetVal = BthCreateSCOConnection (&m_AGProps.btAddrClient, &m_hSco); 
        if (ERROR_SUCCESS != dwRetVal) { 
            DEBUGMSG(ZONE_ERROR, (_T("BTAGSVC: Error creating SCO connection: %d\n"), dwRetVal)); 
            //retry to work around certain cases where the SCO connection seems to be oddly rejected by the HF unit 
            Sleep(100); 
            dwRetVal = BthCreateSCOConnection (&m_AGProps.btAddrClient, &m_hSco); 
            if (ERROR_SUCCESS != dwRetVal) { 
                DEBUGMSG(ZONE_ERROR, (_T("BTAGSVC: Error creating SCO connection: %d\n"), dwRetVal));             
                goto exit; 
            } 
        } 
    } 
 
    // TODO: See if we have a built-in audio driver and do switching. 
 
    waveOutMessage(0, WODM_BT_SCO_AUDIO_CONTROL, 0, TRUE); 
 
    m_AGProps.state = AG_STATE_AUDIO_UP; 
 
    AddRef(); 
    Unlock(); 
 
    BthAGPhoneExtEvent(AG_PHONE_EVENT_BT_AUDIO, 1, NULL); 
 
    Lock(); 
    DelRef(); 
     
exit: 
    return dwRetVal; 
} 
 
 
// This private method closes the audio connection to the peer 
DWORD CAGEngine::CloseAudioChannel(void) 
{ 
    DWORD dwRetVal = ERROR_SUCCESS; 
 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: ++CloseAudioChannel\n")); 
 
    ASSERT(IsLocked()); 
     
    if (m_AGProps.state >= AG_STATE_AUDIO_UP) { 
        DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: CloseAudioChannel - audio is up, closing connection\n")); 
 
        waveOutMessage(0, WODM_BT_SCO_AUDIO_CONTROL, 0, FALSE); 
 
        // TODO: See if we have a built-in audio driver and do switching. 
 
        if (m_AGProps.fPCMMode) { 
            DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: CloseAudioChannel - Need to close SCO connection in PCM mode\n")); 
             
            dwRetVal = BthCloseConnection (m_hSco); 
            if (dwRetVal != ERROR_SUCCESS) { 
                DEBUGMSG(ZONE_WARN, (_T("BTAGSVC: Error closing SCO connection: %d\n"), dwRetVal)); 
            } 
 
            m_hSco = 0;  
        } 
         
        AddRef(); 
        Unlock(); 
         
        BthAGPhoneExtEvent(AG_PHONE_EVENT_BT_AUDIO, 0, NULL); 
 
        Lock(); 
        DelRef();            
 
        m_AGProps.state = AG_STATE_CONNECTED; 
 
        // If we have not already scheduled sniff thread and sniff mode is enabled and we 
        // are connected to a HF device and are persisting HF connections, schedule the 
        // sniff thread. 
        if ((! m_SniffCookie) && m_AGProps.ulSniffDelay && m_AGProps.fHandsfree && !m_AGProps.fPowerSave) { 
            m_SniffCookie = m_pTP->ScheduleEvent(EnterSniffModeThread, (LPVOID)this, m_AGProps.ulSniffDelay);         
        } 
    } 
 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: --CloseAudioChannel\n")); 
     
    return dwRetVal; 
} 
 
 
// This method sets the speaker volume for the peer device 
void CAGEngine::SetSpeakerVolume(unsigned short usVolume) 
{ 
    CHAR szBuf[MAX_SEND_BUF]; 
     
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Setting the speaker volume to %d\n", usVolume)); 
 
    ASSERT(usVolume <= 15); 
 
    Lock(); 
 
    m_AGProps.usSpeakerVolume = usVolume; 
    if (m_AGProps.state >= AG_STATE_CONNECTING) { 
        int cbBuf = _snprintf(szBuf, MAX_SEND_BUF-1, AT_VGS, usVolume); 
        szBuf[MAX_SEND_BUF-1]='\0'; 
        if (cbBuf > 0) { 
            SendATCommand(szBuf, cbBuf);     
        } 
    } 
     
    Unlock(); 
} 
 
 
// This method sets the mic volume for the peer device 
void CAGEngine::SetMicVolume(unsigned short usVolume) 
{ 
    CHAR szBuf[MAX_SEND_BUF]; 
 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Setting the microphone volume to %d\n", usVolume)); 
 
    ASSERT(usVolume <= 15); 
 
    Lock(); 
 
    m_AGProps.usMicVolume = usVolume; 
    int cbBuf = _snprintf(szBuf, MAX_SEND_BUF-1, AT_VGM, usVolume); 
    szBuf[MAX_SEND_BUF-1]='\0'; 
    if (cbBuf > 0) { 
        if (m_AGProps.state >= AG_STATE_CONNECTING) { 
            SendATCommand(szBuf, cbBuf); 
        } 
    } 
 
    Unlock(); 
} 
 
 
// This method is called when the headset button is pressed 
void CAGEngine::OnHeadsetButton(LPSTR pszParams, int cchParam) 
{ 
    DWORD dwErr = ERROR_SUCCESS; 
     
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Calling AGEngine::OnHeadsetButton\n")); 
 
    Lock(); 
 
    ASSERT(m_AGProps.state >= AG_STATE_CONNECTED); 
 
    SendATCommand(AT_OK, sizeof(AT_OK) - 1); 
 
    if ((AG_STATE_RINGING == m_AGProps.state) || (AG_STATE_RINGING_AUDIO_UP == m_AGProps.state)) { 
        DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Calling AGEngine::OnHeadsetButton - In ringing state, answer call.\n")); 
 
        m_AGProps.fUseHFAudio = TRUE; 
         
        AddRef(); 
        Unlock(); 
         
        dwErr = BthAGNetworkAnswerCall(); 
         
        Lock(); 
        DelRef(); 
 
        m_fExpectHeadsetButton = FALSE; 
 
        if (ERROR_SUCCESS != dwErr) { 
            DEBUGMSG(ZONE_ERROR, (L"BTAGSVC: Error answering call: %d\n", dwErr)); 
            m_AGProps.fUseHFAudio = FALSE; 
        } 
    } 
    else if (! m_fExpectHeadsetButton) { 
        DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Calling AGEngine::OnHeadsetButton - Not expecting headset button, drop/swap call.\n")); 
 
        AddRef(); 
        Unlock(); 
 
        DWORD dwState = 0; 
        BthAGNetworkGetCallState(&dwState); 
 
        if ((dwState & NETWORK_FLAGS_STATE_HOLD)    ||   // If bitmask indicates we have calls on hold, do a swap 
            ((dwState & NETWORK_FLAGS_STATE_ACTIVE) &&  
            (dwState & NETWORK_FLAGS_STATE_OFFERING))    // If we have active call and offering call, do a swap 
            ) { 
            m_AGProps.fUseHFAudio = TRUE; 
            dwErr = BthAGNetworkSwapCall(); 
        } 
        else {                 
            BthAGNetworkDropCall(NETWORK_FLAGS_DROP_ACTIVE); 
        } 
         
        Lock(); 
        DelRef(); 
 
        if (ERROR_SUCCESS != dwErr) { 
            DEBUGMSG(ZONE_ERROR, (L"BTAGSVC: Error swapping call: %d\n", dwErr)); 
            m_AGProps.fUseHFAudio = FALSE; 
        } 
    } 
    else { 
        // After headset-initiated connection, the HeadsetButton command 
        // is expected once. 
        DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Calling AGEngine::OnHeadsetButton - Expecting headset button after connect, do nothing.\n")); 
        m_fExpectHeadsetButton = FALSE; 
    } 
 
    Unlock(); 
} 
 
 
// This method is called when the speaker volume on the device changes 
void CAGEngine::OnSpeakerVol(LPSTR pszParams, int cchParam) 
{ 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Calling AGEngine::OnSpkVol\n")); 
 
    Lock(); 
 
    ASSERT(m_AGProps.state >= AG_STATE_CONNECTING); 
 
    LPSTR pTmp;     
    unsigned short usVolume = (USHORT) strtol(pszParams, &pTmp, 10); 
    if (pTmp != (pszParams + strlen(pszParams))) { 
        DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Speaker volume AT Command was poorly formatted.\n")); 
        SendATCommand(AT_ERROR, sizeof(AT_ERROR)-1); 
    } 
    else if (usVolume > 15) { 
        DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Received speaker volume indication of %d - returning ERROR (out of range).\n", usVolume)); 
        SendATCommand(AT_ERROR, sizeof(AT_ERROR)-1); 
    } 
    else { 
        m_AGProps.usSpeakerVolume = usVolume; 
        SendATCommand(AT_OK, sizeof(AT_OK)-1);      
         
        AddRef(); 
        Unlock();         
         
        BthAGPhoneExtEvent(AG_PHONE_EVENT_SPEAKER_VOLUME, (DWORD)usVolume, NULL);         
         
        Lock(); 
        DelRef(); 
    } 
 
    Unlock(); 
} 
 
 
// This method is called when the mic volume on the device changes 
void CAGEngine::OnMicVol(LPSTR pszParams, int cchParam) 
{ 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Calling AGEngine::OnMicVol\n")); 
 
    Lock(); 
 
    ASSERT(m_AGProps.state >= AG_STATE_CONNECTING); 
 
    LPSTR pTmp;     
    unsigned short usVolume = (USHORT) strtol(pszParams, &pTmp, 10); 
    if (pTmp != (pszParams + strlen(pszParams))) { 
        DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Mic volume AT Command was poorly formatted.\n")); 
        SendATCommand(AT_ERROR, sizeof(AT_ERROR)-1); 
    } 
    else if (usVolume > 15) { 
        DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Received microphone volume indication of %d - returning ERROR (out of range).\n", usVolume)); 
        SendATCommand(AT_ERROR, sizeof(AT_ERROR)-1); 
    } 
    else { 
        m_AGProps.usMicVolume = usVolume; 
        SendATCommand(AT_OK, sizeof(AT_OK)-1); 
         
        AddRef(); 
        Unlock(); 
         
        BthAGPhoneExtEvent(AG_PHONE_EVENT_MIC_VOLUME, (DWORD)usVolume, NULL);  
         
        Lock(); 
        DelRef();         
    } 
 
    Unlock(); 
} 
 
 
// This method is called when the peer device dials a number 
void CAGEngine::OnDial(LPSTR pszParams, int cchParam) 
{ 
    WCHAR wszNumber[MAX_PHONE_NUMBER]; 
    DWORD dwErr; 
 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Calling AGEngine::OnDial\n")); 
 
    Lock(); 
 
    ASSERT(m_AGProps.state >= AG_STATE_CONNECTED); 
     
    SendATCommand(AT_OK, sizeof(AT_OK)-1); 
 
    if (pszParams[cchParam-1] == ';') { 
        pszParams[cchParam-1] = '\0'; 
    } 
 
    if (m_AGProps.fIndicatorUpdates) { 
        SendATCommand("\r\n+CIEV:3,2\r\n", 13); // Call Setup is ongoing 
    } 
    m_AGProps.usCallSetup = 2; 
 
    m_AGProps.fUseHFAudio = TRUE; // Indicate call is initiated from HF 
 
    AddRef(); 
    Unlock(); 
     
    if (0 < MultiByteToWideChar(CP_ACP, 0, pszParams, -1, wszNumber, ARRAY_SIZE(wszNumber))) { 
        dwErr = BthAGNetworkDialNumber(wszNumber); 
    } 
    else { 
        ASSERT(0); 
        dwErr = GetLastError(); 
    } 
     
    Lock(); 
    DelRef(); 
 
    if (ERROR_SUCCESS != dwErr) { 
        DEBUGMSG(ZONE_ERROR, (L"BTAGSVC: Error dialing number: %d\n", dwErr)); 
 
        if (m_AGProps.fIndicatorUpdates) { 
            SendATCommand("\r\n+CIEV:3,0\r\n", 13); // Call Setup is done 
        } 
        m_AGProps.usCallSetup = 0; 
        m_AGProps.fUseHFAudio = FALSE; 
    } 
 
    Unlock(); 
} 
 
 
// This method is called when the peer device wants to dial the last dialed number 
void CAGEngine::OnDialLast(LPSTR pszParams, int cchParam) 
{ 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Calling AGEngine::OnDialLast\n")); 
    WCHAR wszNumber[MAX_PHONE_NUMBER]; 
 
    Lock(); 
 
    ASSERT(m_AGProps.state >= AG_STATE_CONNECTED); 
 
    SendATCommand(AT_OK, sizeof(AT_OK)-1); 
 
    AddRef(); 
    Unlock(); 
     
    BOOL fDialed = BthAGGetLastDialed(wszNumber); 
     
    Lock(); 
    DelRef(); 
 
    if (fDialed) {         
        if (m_AGProps.fIndicatorUpdates) { 
            SendATCommand("\r\n+CIEV:3,2\r\n", 13); // Call Setup is ongoing 
        } 
        m_AGProps.usCallSetup = 2; 
 
        m_AGProps.fUseHFAudio = TRUE; // Indicate call is initiated from HF 
 
        AddRef(); 
        Unlock();         
         
        DWORD dwErr = BthAGNetworkDialNumber(wszNumber); 
         
        Lock(); 
        DelRef(); 
 
        if (ERROR_SUCCESS != dwErr) { 
            DEBUGMSG(ZONE_ERROR, (L"BTAGSVC: Error dialing number: %d\n", dwErr)); 
            if (m_AGProps.fIndicatorUpdates) { 
                SendATCommand("\r\n+CIEV:3,0\r\n", 13); // Call Setup is done 
            } 
            m_AGProps.usCallSetup = 0; 
            m_AGProps.fUseHFAudio = FALSE; 
        } 
    } 
 
    Unlock(); 
} 
 
 
// This method is called when the peer device wants to dial a number in memory 
void CAGEngine::OnDialMemory(LPSTR pszParams, int cchParam) 
{ 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Calling AGEngine::OnDialMemory\n")); 
         
    if (pszParams[cchParam-1] == ';') { 
        pszParams[cchParam-1] = '\0'; 
    } 
 
    Lock(); 
 
    ASSERT(m_AGProps.state >= AG_STATE_CONNECTED); 
 
    LPSTR pTmp;  
    unsigned short usIndex = (USHORT) strtol(pszParams, &pTmp, 10); 
    if (pTmp != (pszParams + strlen(pszParams))) { 
        DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: ATD> Command was poorly formatted.\n")); 
        SendATCommand(AT_ERROR, sizeof(AT_ERROR)-1); 
    } 
    else { 
        WCHAR wszNumber[MAX_PHONE_NUMBER]; 
             
        SendATCommand(AT_OK, sizeof(AT_OK)-1); 
 
        AddRef(); 
        Unlock(); 
 
        BOOL fDialed = BthAGGetSpeedDial(usIndex, wszNumber); 
 
        Lock(); 
        DelRef(); 
        
        if (fDialed) { 
            if (m_AGProps.fIndicatorUpdates) { 
                SendATCommand("\r\n+CIEV:3,2\r\n", 13); // Call Setup is ongoing 
            } 
            m_AGProps.usCallSetup = 2; 
 
            m_AGProps.fUseHFAudio = TRUE; // Indicate call is initiated from HF 
 
            AddRef(); 
            Unlock(); 
             
            DWORD dwErr = BthAGNetworkDialNumber(wszNumber);             
             
            Lock(); 
            DelRef(); 
 
            if (ERROR_SUCCESS != dwErr) {                 
                DEBUGMSG(ZONE_ERROR, (L"BTAGSVC: Error dialing number: %d\n", dwErr)); 
                if (m_AGProps.fIndicatorUpdates) { 
                    SendATCommand("\r\n+CIEV:3,0\r\n", 13); // Call Setup is done 
                } 
                m_AGProps.usCallSetup = 0; 
                m_AGProps.fUseHFAudio = FALSE; 
            } 
        } 
    } 
 
    Unlock(); 
} 
 
 
// This method is called when the peer device to answer an incoming call 
void CAGEngine::OnAnswerCall(LPSTR pszParams, int cchParam) 
{ 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Calling AGEngine::OnAnswerCall\n")); 
 
    Lock(); 
 
    if (m_AGProps.state >= AG_STATE_RINGING) { 
        m_AGProps.fMuteRings = TRUE; 
 
        m_AGProps.fUseHFAudio = TRUE; // Indicate call is answered from HF 
 
        AddRef(); 
        Unlock(); 
         
        DWORD dwErr = BthAGNetworkAnswerCall(); 
         
        Lock(); 
        DelRef(); 
       
        if (ERROR_SUCCESS == dwErr) { 
            SendATCommand(AT_OK, sizeof(AT_OK)-1); 
        } 
        else { 
            DEBUGMSG(ZONE_ERROR, (L"BTAGSVC: Error answering call: %d\n", dwErr)); 
            SendATCommand(AT_ERROR, sizeof(AT_ERROR)-1); 
            m_AGProps.fUseHFAudio = FALSE; 
        } 
    } 
    else { 
        SendATCommand(AT_ERROR, sizeof(AT_ERROR)-1); 
    } 
 
    Unlock(); 
} 
 
 
// This method is called by the peer device to hang-up or reject an incoming call 
void CAGEngine::OnHangupCall(LPSTR pszParam, int cchParam) 
{ 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Calling AGEngine::OnHangupCall\n")); 
 
    Lock(); 
     
    if (((m_AGProps.state == AG_STATE_RINGING) ||  
        (m_AGProps.state == AG_STATE_RINGING_AUDIO_UP)) &&  
        (!(m_AGProps.usHFCapability & AG_CAP_REJECT_CALL))) 
    { 
        // Do not support rejecting incoming calls 
        SendATCommand(AT_ERROR, sizeof(AT_ERROR)-1); 
    } 
    else { 
        AddRef(); 
        Unlock(); 
         
        DWORD dwErr = BthAGNetworkDropCall(NETWORK_FLAGS_DROP_ACTIVE); 
         
        Lock(); 
        DelRef(); 
 
        if (ERROR_SUCCESS == dwErr) {     
            SendATCommand(AT_OK, sizeof(AT_OK)-1); 
        } 
        else { 
            DEBUGMSG(ZONE_ERROR, (L"BTAGSVC: Error dropping call: %d\n", dwErr)); 
            SendATCommand(AT_ERROR, sizeof(AT_ERROR)-1);             
        } 
    } 
     
    Unlock(); 
} 
 
 
// This method is called by the peer to transmit DTMF codes 
void CAGEngine::OnDTMF(LPSTR pszParams, int cchParam) 
{ 
    WCHAR wszDTMF[MAX_SEND_BUF]; 
     
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Calling AGEngine::OnDTMF\n")); 
 
    Lock(); 
 
    ASSERT(m_AGProps.state >= AG_STATE_CONNECTED); 
 
    if (m_AGProps.fInCall) { 
        SendATCommand(AT_OK, sizeof(AT_OK)-1); 
 
        AddRef(); 
        Unlock(); 
         
        if (0 < MultiByteToWideChar(CP_ACP, 0, pszParams, -1, wszDTMF, ARRAY_SIZE(wszDTMF))) { 
            BthAGNetworkTransmitDTMF(wszDTMF); 
        } 
        else { 
            ASSERT(0); 
        }                
         
        Lock(); 
        DelRef(); 
    } 
    else { 
        SendATCommand(AT_ERROR, sizeof(AT_ERROR)-1); 
    } 
 
    Unlock(); 
} 
 
 
// This method is called by the peer to place a call on hold or enable 
// a multi-party call. 
void CAGEngine::OnCallHold(LPSTR pszParams, int cchParam) 
{ 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Calling AGEngine::OnCallHold\n")); 
 
    Lock(); 
 
    ASSERT(m_AGProps.state >= AG_STATE_CONNECTED); 
 
    DWORD dwErr; 
    BOOL fSuccess = FALSE; 
    BOOL fServiceConnectionUp = FALSE; 
    BOOL fCallSetup = FALSE; 
 
    if (m_AGProps.usHFCapability & AG_CAP_3WAY_CALL) { 
        if (cchParam < 1) { 
            // Bad parameters, do nothing to send error 
        } 
        else if (pszParams[0] == '?') { 
            // Test command for supported call-waiting features             
            SendATCommand("\r\n+CHLD:(0,1,2)\r\n", 17); 
            SendATCommand(AT_OK, sizeof(AT_OK)-1); 
            fServiceConnectionUp = TRUE; 
            fSuccess = TRUE; 
        } 
        else if (pszParams[0] == '0') { 
            // Release all calls 
            AddRef(); 
            Unlock(); 
 
            dwErr = BthAGNetworkDropCall(NETWORK_FLAGS_DROP_ALL); 
 
            Lock(); 
            DelRef(); 
             
            if (ERROR_SUCCESS == dwErr) { 
                fSuccess = TRUE; 
                SendATCommand(AT_OK, sizeof(AT_OK)-1); 
            } 
        } 
        else if (pszParams[0] == '1') { 
            // Release active calls, transfer held calls 
            m_AGProps.fUseHFAudio = TRUE; 
 
            AddRef(); 
            Unlock(); 
 
            dwErr = BthAGNetworkDropCall(NETWORK_FLAGS_DROP_ACTIVE); 
 
            Lock(); 
            DelRef(); 
             
            if (ERROR_SUCCESS == dwErr) { 
                m_AGProps.fUseHFAudio = TRUE; 
 
                AddRef(); 
                Unlock(); 
 
                dwErr = BthAGNetworkUnholdCall(); 
 
                Lock(); 
                DelRef(); 
                 
                if (ERROR_SUCCESS == dwErr) { 
                    fSuccess = TRUE; 
                    SendATCommand(AT_OK, sizeof(AT_OK)-1); 
                } 
            } 
        } 
        else if (pszParams[0] == '2') { 
            // Swap calls 
            m_AGProps.fUseHFAudio = TRUE; 
 
            AddRef(); 
            Unlock(); 
 
            dwErr = BthAGNetworkSwapCall(); 
 
            Lock(); 
            DelRef(); 
             
            if (ERROR_SUCCESS == dwErr) { 
                fSuccess = TRUE; 
                SendATCommand(AT_OK, sizeof(AT_OK)-1); 
            } 
        } 
        else if ((pszParams[0] == '3') || (pszParams[0] == '4')) { 
            // We don't support these parameters, do nothing to send error 
        } 
    } 
 
    if (! fSuccess) { 
        SendATCommand(AT_ERROR, sizeof(AT_ERROR)-1); 
        m_AGProps.fUseHFAudio = TRUE; 
    } 
 
    if (fServiceConnectionUp) { 
        ServiceConnectionUp(); 
    } 
 
    Unlock(); 
} 
 
 
PFN_BthAGOnVoiceTag CAGEngine::GetVoiceTagHandler() 
{ 
    return m_pParser ? m_pParser->GetVoiceTagHandler() : NULL; 
} 
 
 
// This method is called by the peer to start voice recognition 
void CAGEngine::OnVoiceRecognition(LPSTR pszParams, int cchParam) 
{ 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Calling AGEngine::OnVoiceRecognition\n")); 
 
    Lock(); 
 
    ASSERT(m_AGProps.state >= AG_STATE_CONNECTED); 
 
    if (m_AGProps.usHFCapability & AG_CAP_VOICE_RECOG) { 
        if (cchParam != 1) { 
            SendATCommand(AT_ERROR, sizeof(AT_ERROR)-1); 
        } 
        else if (pszParams[0] == '0') { 
            SendATCommand(AT_OK, sizeof(AT_OK)-1); 
             
            AddRef(); 
            Unlock(); 
             
            BthAGPhoneExtEvent(AG_PHONE_EVENT_VOICE_RECOG, 0, (void*)GetVoiceTagHandler()); 
 
            Lock(); 
            DelRef(); 
        } 
        else if (pszParams[0] == '1') { 
            SendATCommand(AT_OK, sizeof(AT_OK)-1); 
            DWORD dwErr = OpenAudioChannel(); 
            if ((ERROR_SUCCESS != dwErr) && (ERROR_ALREADY_INITIALIZED != dwErr)) { 
                DEBUGMSG(ZONE_WARN, (L"BTAGSVC: Call to open audio channel failed.  Can't do voice recognition.\n")); 
            }         
 
            AddRef(); 
            Unlock(); 
             
            BthAGPhoneExtEvent(AG_PHONE_EVENT_VOICE_RECOG, 1, (void*)GetVoiceTagHandler()); 
 
            Lock(); 
            DelRef();             
        }     
    } 
    else { 
        SendATCommand(AT_ERROR, sizeof(AT_ERROR)-1);         
    } 
     
    Unlock(); 
} 
 
 
// This method is called by the peer to query supported features of the AG 
void CAGEngine::OnSupportedFeatures(LPSTR pszParams, int cchParam) 
{ 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Calling AGEngine::OnSupportedFeatures\n")); 
 
    Lock(); 
 
    ASSERT(m_AGProps.state >= AG_STATE_CONNECTING); 
 
    LPSTR pTmp;     
    m_AGProps.usRemoteFeatures = (USHORT) strtol(pszParams, &pTmp, 10); 
    if (pTmp != (pszParams + strlen(pszParams))) { 
        DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: AT+BRSF Command was poorly formatted.\n")); 
        SendATCommand(AT_ERROR, sizeof(AT_ERROR)-1); 
    } 
    else { 
        CHAR pszCommand[MAX_SEND_BUF]; 
         
        m_AGProps.fHandsfree = TRUE; 
         
        int cchCommand = _snprintf(pszCommand, MAX_SEND_BUF-1, "\r\n+BRSF:%d\r\n", m_AGProps.usHFCapability); 
        pszCommand[MAX_SEND_BUF-1]='\0'; 
        if (cchCommand > 0) { 
            SendATCommand(pszCommand, cchCommand); 
            SendATCommand(AT_OK, sizeof(AT_OK)-1); 
        } 
        else { 
            SendATCommand(AT_ERROR, sizeof(AT_ERROR)-1); 
        } 
    }        
 
    Unlock(); 
} 
 
 
// This method is called by the peer to get a list of supported indicators 
void CAGEngine::OnTestIndicators(LPSTR pszParams, int cchParam) 
{ 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Calling AGEngine::OnTestIndicators\n")); 
 
    CHAR pszCommand[MAX_SEND_BUF]; 
 
    Lock(); 
 
    ASSERT(m_AGProps.state >= AG_STATE_CONNECTING); 
 
    m_AGProps.fHandsfree = TRUE; 
 
    int cchCommand = _snprintf(pszCommand, MAX_SEND_BUF-1, "\r\n+CIND: (\"service\",(0,1)),(\"call\",(0,1)),(\"callsetup\",(0-3))\r\n"); 
    pszCommand[MAX_SEND_BUF-1]='\0'; 
    if (cchCommand > 0) { 
        SendATCommand(pszCommand, cchCommand); 
        SendATCommand(AT_OK, sizeof(AT_OK)-1); 
    } 
    else { 
        SendATCommand(AT_ERROR, sizeof(AT_ERROR)-1); 
    } 
 
    Unlock(); 
} 
 
 
// This method is called by the peer to read the supported indicators 
void CAGEngine::OnReadIndicators(LPSTR pszParams, int cchParam) 
{ 
    CHAR pszCommand[MAX_SEND_BUF]; 
     
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Calling AGEngine::OnReadIndicators\n")); 
 
    Lock(); 
 
    ASSERT(m_AGProps.state >= AG_STATE_CONNECTING); 
 
    m_AGProps.fHandsfree = TRUE; 
 
    BOOL fService = (m_AGProps.fHaveService?1:0); 
    BOOL fCall = ((m_AGProps.fInCall)?1:0); 
 
    // Send status of all indicators 
    int cchCommand = _snprintf(pszCommand, MAX_SEND_BUF-1, "\r\n+CIND: %d,%d,%d\r\n", fService, fCall, m_AGProps.usCallSetup); 
    pszCommand[MAX_SEND_BUF-1]='\0'; 
    if (cchCommand > 0) { 
        SendATCommand(pszCommand, cchCommand); 
        SendATCommand(AT_OK, sizeof(AT_OK)-1); 
    } 
    else { 
        SendATCommand(AT_ERROR, sizeof(AT_ERROR)-1); 
    } 
 
    Unlock(); 
} 
 
 
// This method is called by the peer to register for indicator updates. 
void CAGEngine::OnRegisterIndicatorUpdates(LPSTR pszParams, int cchParam) 
{ 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Calling AGEngine::OnRegisterIndicatorUpdates\n")); 
 
    // 
    // Format of the string we are parsing: "a,b,c,d\n". 
    // We care about a and d. 
    // 
 
    Lock(); 
 
    ASSERT(m_AGProps.state >= AG_STATE_CONNECTING); 
 
    DWORD cParam = 0; 
    BOOL fSendOk = FALSE; 
 
    while (*pszParams != '\0') { 
        if (*pszParams == ',') { 
            cParam++; 
        } 
        else if (cParam == 0 && (*pszParams != ' ')) { 
            // If first param is not 3 we don't care about this command 
            if (*pszParams != '3') { 
                break; 
            } 
        } 
        else if ((cParam == 3) && (*pszParams != ' ')) { 
            // Fourth param is indicator update flag 
            if (*pszParams == '1') {             
                DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Enabling Indicator Updates.\n")); 
                m_AGProps.fIndicatorUpdates = TRUE; 
            } 
            else {             
                DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Disabling Indicator Updates.\n")); 
                m_AGProps.fIndicatorUpdates = FALSE; 
            } 
 
            m_AGProps.fHandsfree = TRUE; 
 
            fSendOk = TRUE; 
            break; 
        } 
 
        pszParams++; 
    } 
 
    if (fSendOk) { 
        SendATCommand(AT_OK, sizeof(AT_OK)-1); 
    } 
    else { 
        SendATCommand(AT_ERROR, sizeof(AT_ERROR)-1); 
    } 
 
    if ((m_AGProps.usHFCapability & AG_CAP_3WAY_CALL) &&  
        (m_AGProps.usRemoteFeatures & HF_CAP_3WAY_CALL)) { 
        // If we mutually support 3-way call, service level connection 
        // is not up until after AT+CHLD=? 
    } 
    else { 
        ServiceConnectionUp(); 
    } 
     
    Unlock(); 
 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: --AGEngine::OnRegisterIndicatorUpdates\n")); 
} 
 
 
// This method is called by the peer to enable call-waiting 
void CAGEngine::OnEnableCallWaiting(LPSTR pszParams, int cchParam) 
{ 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Calling AGEngine::OnEnableCallWaiting\n")); 
 
    Lock(); 
 
    ASSERT(m_AGProps.state >= AG_STATE_CONNECTING); 
 
    BOOL fSendOk = FALSE; 
 
    while (*pszParams != '\0') { 
        if (*pszParams != ' ') { 
            if (*pszParams == '1') { 
                DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Enabling Call Waiting.\n")); 
                m_AGProps.fNotifyCallWait = TRUE; 
            } 
            else { 
                DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Disabling Call Waiting.\n")); 
                m_AGProps.fNotifyCallWait = FALSE; 
            } 
 
            fSendOk = TRUE; 
            break; 
        } 
        pszParams++; 
    } 
 
    if (fSendOk) { 
        SendATCommand(AT_OK, sizeof(AT_OK)-1); 
    } 
    else { 
        SendATCommand(AT_ERROR, sizeof(AT_ERROR)-1); 
    } 
 
    Unlock(); 
 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: --AGEngine::OnEnableCallWaiting\n")); 
} 
 
 
// This method is called by the peer to enable "call line identification". 
void CAGEngine::OnEnableCLI(LPSTR pszParams, int cchParam) 
{ 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Calling AGEngine::OnEnableCLI\n")); 
 
    BOOL fSendOk = FALSE; 
 
    Lock(); 
 
    ASSERT(m_AGProps.state >= AG_STATE_CONNECTING); 
 
    while (*pszParams != '\0') { 
        if (*pszParams != ' ') { 
            if (*pszParams == '1') { 
                DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Enabling CLI.\n")); 
                m_AGProps.fNotifyCLI = TRUE; 
            } 
            else { 
                DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Disabling CLI.\n")); 
                m_AGProps.fNotifyCLI = FALSE; 
            } 
 
            fSendOk = TRUE; 
            break; 
        } 
        pszParams++; 
    } 
 
    if (fSendOk) { 
        SendATCommand(AT_OK, sizeof(AT_OK)-1); 
    } 
    else { 
        SendATCommand(AT_ERROR, sizeof(AT_ERROR)-1); 
    } 
 
    Unlock(); 
} 
 
 
// This method is called when the parser receives an unknown command 
void CAGEngine::OnUnknownCommand(void) 
{ 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Calling AGEngine::OnUnknownCommand\n")); 
 
    Lock(); 
 
    ASSERT(m_AGProps.state >= AG_STATE_CONNECTING); 
     
    if (m_AGProps.state >= AG_STATE_CONNECTING) { 
        SendATCommand(AT_ERROR, sizeof(AT_ERROR)-1); 
    } 
     
    Unlock(); 
} 
 
 
// This method is called when the peer device responds with OK 
void CAGEngine::OnOK(void) 
{ 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Got OK response from peer.\n")); 
} 
 
 
// This method is called when the peer devices responds with ERROR 
void CAGEngine::OnError(void) 
{ 
    DEBUGMSG(ZONE_WARN, (L"BTAGSVC: Got ERROR response from peer.\n")); 
} 
 
 
// This method is called when service goes up or down 
void CAGEngine::OnServiceCallback(BOOL fHaveService) 
{ 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: OnServiceCallback: %d\n", fHaveService)); 
 
    if (m_AGProps.fHaveService != fHaveService) { 
        Lock(); 
         
        m_AGProps.fHaveService = fHaveService; 
        if (m_AGProps.fIndicatorUpdates) { 
            CHAR szCommand[MAX_SEND_BUF];             
            int cbCommand = _snprintf(szCommand, MAX_SEND_BUF-1, "\r\n+CIEV:1,%d\r\n", (fHaveService?1:0)); 
            szCommand[MAX_SEND_BUF-1]='\0'; 
            if (cbCommand > 0) { 
                SendATCommand(szCommand, cbCommand); 
            } 
        } 
 
        Unlock(); 
    } 
} 
 
 
// This method is called by the Network when an event has occured 
void CAGEngine::OnNetworkEvent(DWORD dwEvent, LPVOID lpvParam, DWORD cbParam) 
{ 
    switch (dwEvent) { 
        case NETWORK_EVENT_CALL_IN: 
            OnNetworkCallIn((LPSTR)lpvParam); 
            break; 
        case NETWORK_EVENT_CALL_OUT: 
            OnNetworkCallOut(); 
            break; 
        case NETWORK_EVENT_CALL_CONNECT: 
            OnNetworkAnswerCall(); 
            break; 
        case NETWORK_EVENT_CALL_BUSY: 
        case NETWORK_EVENT_CALL_DISCONNECT: 
            if (sizeof(DWORD) > cbParam) { 
                DEBUGMSG(ZONE_ERROR, (L"BTAGSVC: Received error event from network component with invalid param size.\n")); 
                ASSERT(0);                     
            } 
            else { 
                OnNetworkHangupCall((DWORD)lpvParam, (NETWORK_EVENT_CALL_BUSY == dwEvent) ? HANGUP_DELAY_BUSY_MS : HANGUP_DELAY_MS); 
            } 
            break; 
        case NETWORK_EVENT_CALL_REJECT:         
            OnNetworkRejectCall(); 
            break; 
        case NETWORK_EVENT_RING: 
            OnNetworkRing(); 
            break; 
        case NETWORK_EVENT_CALL_INFO: 
            OnNetworkInfo((LPSTR)lpvParam); 
            break; 
        case NETWORK_EVENT_FAILED: 
            if (sizeof(NetworkCallFailedInfo) > cbParam) { 
                DEBUGMSG(ZONE_ERROR, (L"BTAGSVC: Received error event from network component with invalid param size.\n")); 
                ASSERT(0);                     
            } 
            else { 
                OnNetworkCallFailed(((NetworkCallFailedInfo*)lpvParam)->usCallType, ((NetworkCallFailedInfo*)lpvParam)->dwStatus); 
            }             
            break;             
        default: 
            DEBUGMSG(ZONE_WARN, (L"BTAGSVC: Unknown network event: %d\n", dwEvent)); 
            ASSERT(0); 
    } 
} 
 
 
// This method is called by the Network when a call is answered 
void CAGEngine::OnNetworkAnswerCall(void) 
{ 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Calling AGEngine::OnNetworkAnswerCall\n")); 
 
    Lock(); 
     
    if ((m_AGProps.state >= AG_STATE_CONNECTED) && m_AGProps.fIndicatorUpdates) { 
        SendATCommand("\r\n+CIEV:2,1\r\n", 13); // Call is active 
        SendATCommand("\r\n+CIEV:3,0\r\n", 13); // Call Setup is completed 
    } 
    m_AGProps.usCallSetup = 0; 
    m_AGProps.fInCall = TRUE; 
 
    if ((m_AGProps.state >= AG_STATE_CONNECTED) && m_AGProps.fUseHFAudio) { 
		if (m_AGProps.usCallType == AG_CALL_TYPE_IN) { 
	        DWORD dwErr = OpenAudioChannel(); 
	        if ((ERROR_SUCCESS != dwErr) && (ERROR_ALREADY_INITIALIZED != dwErr)) { 
	            DEBUGMSG(ZONE_ERROR, (L"BTAGSVC: Error opening audio connection: %d\n", dwErr)); 
	        } 
		} 
    } 
    else if (m_AGProps.fPowerSave || !m_AGProps.fHandsfree) { 
        // Call was answered in the handset close connection in power-save mode 
        CloseControlChannel(); 
    } 
 
    m_AGProps.fUseHFAudio = FALSE; 
 
    Unlock(); 
} 
 
 
// This method is called by the Network when a call is disconnected (or busy) 
void CAGEngine::OnNetworkHangupCall(DWORD dwRemainingConnections, DWORD dwHangUpDelayMS) 
{ 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Calling AGEngine::OnNetworkHangupCall\n")); 
 
    Lock(); 
 
    if (dwRemainingConnections == 0) { 
        if (m_AGProps.state >= AG_STATE_CONNECTED) { 
            if (m_AGProps.fIndicatorUpdates && m_AGProps.fInCall) { 
                // For hangup call, send call indicator as inactive 
                SendATCommand("\r\n+CIEV:2,0\r\n", 13);  
            } 
            else if (m_AGProps.fIndicatorUpdates) { 
                // For rejected call, send callsetup indicator as inactive 
                SendATCommand("\r\n+CIEV:3,0\r\n", 13);  
            } 
 
            wcscpy(m_wszCLI, L""); 
 
            m_fCancelCloseConnection = FALSE; 
            m_CloseCookie = m_pTP->ScheduleEvent(TimeoutConnection, (LPVOID)this, HANGUP_DELAY_MS); 
        } 
         
        m_AGProps.usCallSetup = 0; 
        m_AGProps.fInCall = FALSE; 
    }    
    else { 
        DWORD dwState = 0; 
        DWORD dwErr = BthAGNetworkGetCallState(&dwState); 
 
        // We have a call connected, if it is not active or on hold, 
        // we need to do some clean up 
         
        if ((ERROR_SUCCESS == dwErr) && 
            !(dwState & NETWORK_FLAGS_STATE_HOLD) &&  
            !(dwState & NETWORK_FLAGS_STATE_ACTIVE)) {             
            if (m_AGProps.fIndicatorUpdates && m_AGProps.fInCall) { 
                // For hangup call, send call indicator as inactive 
                SendATCommand("\r\n+CIEV:2,0\r\n", 13);  
            } 
 
            wcscpy(m_wszCLI, L""); 
            m_AGProps.fInCall = FALSE; 
        }         
    } 
 
    Unlock(); 
} 
 
 
// This method is called by the Network when a call is rejected 
void CAGEngine::OnNetworkRejectCall(void) 
{ 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Calling AGEngine::OnNetworkRejectCall\n")); 
 
    Lock(); 
 
    m_AGProps.usCallSetup = 0; 
    m_AGProps.fInCall = FALSE; 
 
    if (m_AGProps.state >= AG_STATE_CONNECTED) { 
        if (m_AGProps.fIndicatorUpdates) { 
            SendATCommand("\r\n+CIEV:3,0\r\n", 13); // Call Setup is complete 
        } 
 
        wcscpy(m_wszCLI, L""); 
 
        CloseAudioChannel(); 
        if (m_AGProps.fPowerSave || !m_AGProps.fHandsfree) { 
            CloseControlChannel(); 
        } 
    } 
     
    Unlock(); 
} 
 
 
// This method is called by the Network when an incoming call is placed 
void CAGEngine::OnNetworkCallIn(LPSTR pszNumber) 
{ 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Calling AGEngine::OnNetworkCallIn\n")); 
 
    Lock(); 
 
    if (m_AGProps.fInCall) { 
        // Already in a call, send call-wait signal but do not accept call. 
        if (m_AGProps.fNotifyCallWait) { 
            CHAR szCommand[MAX_SEND_BUF]; 
            int cbCommand; 
             
            if (pszNumber[0] == 0) { 
                cbCommand = _snprintf(szCommand, MAX_SEND_BUF-1, "\r\n+CCWA:\"0\",0,1\r\n"); 
            } 
            else { 
                cbCommand = _snprintf(szCommand, MAX_SEND_BUF-1, "\r\n+CCWA:\"%s\",0,1\r\n", pszNumber); 
            } 
            szCommand[MAX_SEND_BUF-1]='\0'; 
            if (cbCommand > 0) { 
                SendATCommand(szCommand, cbCommand); 
            } 
 
            SendATCommand("\r\n+CIEV:3,1\r\n", 13); 
        } 
    } 
    else { 
        m_AGProps.usCallType = AG_CALL_TYPE_IN; 
        m_AGProps.fMuteRings = FALSE;         
         
        DWORD dwErr = OpenControlChannel(FALSE); 
        if ((ERROR_SUCCESS != dwErr) && (ERROR_ALREADY_INITIALIZED != dwErr)) { 
            DEBUGMSG(ZONE_ERROR, (L"BTAGSVC: Error opening AG Connection: %d.\n", dwErr)); 
        } 
        else { 
            if (! m_hUIThread) { 
                m_hUIThread = CreateThread(NULL, 0, AGUIThread, this, 0, NULL); 
            } 
 
            if (m_hUIThread) { 
                if (m_AGProps.fHandsfree) { 
                    if (m_AGProps.usHFCapability & AG_CAP_INBAND_RING) { 
                        if (m_AGProps.state >= AG_STATE_AUDIO_UP) { 
                            DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Incoming call is using in-band ring tone.\n")); 
                            SendATCommand("\r\n+BSIR:1\r\n", 11); // In-band ring tone on 
                        } 
                        else if (m_AGProps.state >= AG_STATE_CONNECTED) { 
                            SendATCommand("\r\n+BSIR:0\r\n", 11); // In-band ring tone off 
                        } 
                    } 
     
                    if (m_AGProps.fIndicatorUpdates) { 
                        SendATCommand("\r\n+CIEV:3,1\r\n", 13); // Call Setup is ongoing 
                    } 
                    m_AGProps.usCallSetup = 1; 
 
                    if (pszNumber[0] != 0) { 
                        WCHAR wszNumber[MAX_PHONE_NUMBER]; 
 
                        ASSERT(ARRAY_SIZE(wszNumber) == ARRAY_SIZE(m_wszCLI)); 
                     
                        if (0 < MultiByteToWideChar(CP_ACP, 0, pszNumber, -1, wszNumber, ARRAY_SIZE(wszNumber))) { 
                            if (m_AGProps.fNotifyCLI) { 
                                if (FALSE == BthAGGetNameByPhoneNumber(pszNumber, m_wszCLI)) { 
                                    wcscpy(m_wszCLI, wszNumber);     
                                }                
                            } 
                            else { 
                                wcscpy(m_wszCLI, wszNumber); 
                            } 
                        } 
                        else { 
                            ASSERT(0); 
                        } 
                    } 
                } 
            } 
            else { 
                DEBUGMSG(ZONE_ERROR, (L"BTAGSVC: Out of resources.\n")); 
            } 
        } 
    } 
     
    Unlock(); 
} 
 
 
// This method is called by the Network when an outgoing call is placed 
void CAGEngine::OnNetworkCallOut(void) 
{ 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Calling AGEngine::OnNetworkCallOut\n")); 
 
    Lock(); 
 
    m_AGProps.usCallType = AG_CALL_TYPE_OUT; 
    m_AGProps.fUseHFAudio = TRUE; // By default outgoing calls will connect HF audio 
 
    m_fAudioConnect = TRUE; 
    DWORD dwErr = OpenControlChannel(FALSE); 
    if ((ERROR_SUCCESS != dwErr) && (ERROR_ALREADY_INITIALIZED != dwErr)) { 
        DEBUGMSG(ZONE_ERROR, (L"BTAGSVC: Error opened AG Connection: %d.\n", dwErr)); 
    } 
    else { 
        if (! m_hUIThread) { 
            m_hUIThread = CreateThread(NULL, 0, AGUIThread, this, 0, NULL); 
        } 
         
        if (m_hUIThread) { 
            if (m_AGProps.fHandsfree) { 
                if (m_AGProps.fIndicatorUpdates) { 
                    SendATCommand("\r\n+CIEV:3,3\r\n", 13); // Call Setup is alerting remote party 
                } 
                m_AGProps.usCallSetup = 3; 
            } 
        } 
        else { 
            DEBUGMSG(ZONE_ERROR, (L"BTAGSVC: Out of resources.\n")); 
        }                 
    } 
 
    Unlock(); 
} 
 
 
// This method is called by the Network when a caller id notification is delayed 
void CAGEngine::OnNetworkInfo(LPSTR pszNumber) 
{ 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Calling AGEngine::OnNetworkInfo number:%hs\n", pszNumber)); 
     
    Lock(); 
     
    if (pszNumber[0] != 0) { 
        WCHAR wszNumber[MAX_PHONE_NUMBER]; 
 
        ASSERT(ARRAY_SIZE(wszNumber) == ARRAY_SIZE(m_wszCLI)); 
         
        if (0 < MultiByteToWideChar(CP_ACP, 0, pszNumber, -1, wszNumber, ARRAY_SIZE(wszNumber))) { 
            if (m_AGProps.fNotifyCLI) { 
                if (FALSE == BthAGGetNameByPhoneNumber(pszNumber, m_wszCLI)) { 
                    wcscpy(m_wszCLI, wszNumber);     
                }                
            } 
            else { 
                wcscpy(m_wszCLI, wszNumber); 
            } 
        } 
        else { 
            ASSERT(0); 
        } 
    } 
     
    Unlock(); 
} 
 
 
// This method is called by the Network when a RING is received 
void CAGEngine::OnNetworkRing(void) 
{ 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Calling AGEngine::OnNetworkRing\n")); 
 
    Lock(); 
     
    if (m_AGProps.state >= AG_STATE_CONNECTED) { 
        if (AG_STATE_AUDIO_UP == m_AGProps.state) { 
            m_AGProps.state = AG_STATE_RINGING_AUDIO_UP; 
        } 
        else if (AG_STATE_CONNECTED == m_AGProps.state) { 
            m_AGProps.state = AG_STATE_RINGING; 
        } 
 
        if (m_AGProps.fHandsfree &&  
            (m_AGProps.usHFCapability & AG_CAP_INBAND_RING) &&  
            (m_AGProps.state != AG_STATE_RINGING_AUDIO_UP)) { 
            // HACK for Nokia carkits.  Carkit might ignore the first BSIR command so let's keep sending them. 
            SendATCommand("\r\n+BSIR:0\r\n", 11); 
        } 
 
        if (!m_AGProps.fMuteRings) { 
            CHAR szCLI[MAX_PHONE_NUMBER]; 
             
            SendATCommand(AT_RING, sizeof(AT_RING)-1); 
 
            if (m_AGProps.fNotifyCLI && (*m_wszCLI != 0) && 
                    (0 < WideCharToMultiByte(CP_ACP, 0, m_wszCLI, -1, szCLI, sizeof(szCLI), NULL, NULL))) { 
                 
                CHAR szCommand[MAX_SEND_BUF]; 
                int cbCommand = _snprintf(szCommand, MAX_SEND_BUF-1, "\r\n+CLIP: \"%s\",0\r\n", szCLI); 
                szCommand[MAX_SEND_BUF-1]='\0'; 
                if (cbCommand > 0) { 
                    SendATCommand(szCommand, cbCommand); 
                } 
            } 
        } 
    } 
 
    Unlock(); 
} 
 
void CAGEngine::OnNetworkCallFailed(USHORT usCallType, DWORD dwStatus) 
{ 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Calling AGEngine::OnNetworkCallFailed error:0x%X\n", dwStatus)); 
 
    Lock(); 
 
    switch (usCallType) { 
        case CALL_TYPE_ANSWER: 
        { 
            DEBUGMSG(ZONE_ERROR, (L"BTAGSVC: Error answering call: 0x%X\n", dwStatus)); 
            m_AGProps.fUseHFAudio = FALSE; 
             
        } 
        break; 
 
        case CALL_TYPE_DROP: 
        { 
            DEBUGMSG(ZONE_ERROR, (L"BTAGSVC: Error dopping call: 0x%X\n", dwStatus)); 
        } 
        break; 
         
        case CALL_TYPE_DIAL: 
        { 
            if (m_AGProps.fIndicatorUpdates) { 
                SendATCommand("\r\n+CIEV:3,0\r\n", 13); // Call Setup is ongoing 
            } 
            m_AGProps.usCallSetup = 0; 
            m_AGProps.fUseHFAudio = FALSE; 
        } 
        break; 
 
        case CALL_TYPE_HOLD: 
        { 
            DEBUGMSG(ZONE_ERROR, (L"BTAGSVC: Error putting call on hold: 0x%X\n", dwStatus)); 
        } 
        break; 
 
        case CALL_TYPE_SWAP: 
        { 
            DEBUGMSG(ZONE_ERROR, (L"BTAGSVC: Error swapping call: 0x%X\n", dwStatus)); 
            m_AGProps.fUseHFAudio = FALSE; 
        } 
        break; 
 
        case CALL_TYPE_UNHOLD: 
        { 
            DEBUGMSG(ZONE_ERROR, (L"BTAGSVC: Error taking call off hold: 0x%X\n", dwStatus)); 
            m_AGProps.fUseHFAudio = FALSE; 
        } 
        break; 
    } 
     
    Unlock(); 
} 
 
 
// This method will unpark or unsniff a connection if necessary 
void CAGEngine::EnsureActiveBaseband(void) 
{ 
    unsigned char mode; 
 
    if ((m_AGProps.state < AG_STATE_AUDIO_UP) && (m_AGProps.state >= AG_STATE_CONNECTING)) {  
        if (ERROR_SUCCESS == BthGetCurrentMode(&m_AGProps.btAddrClient, &mode)) { 
            if (mode == 0x02) { 
                DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: UnSniff.\n")); 
                if (ERROR_SUCCESS != BthExitSniffMode(&m_AGProps.btAddrClient)) { 
                    DEBUGMSG(ZONE_WARN, (L"BTAGSVC: Warning - failed to exit sniff mode.\n")); 
                } 
            } 
            else if (mode == 0x03) { 
                DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: UnPark.\n")); 
                if (ERROR_SUCCESS != BthExitParkMode(&m_AGProps.btAddrClient)) { 
                    DEBUGMSG(ZONE_WARN, (L"BTAGSVC: Warning - failed to exit park mode.\n")); 
                } 
            } 
        } 
    } 
} 
 
 
// This public method sends a custom AT command to the peer device 
DWORD CAGEngine::ExternalSendATCommand(LPSTR pszCommand, unsigned int cbCommand) 
{ 
    Lock(); 
    DWORD dwRetVal = SendATCommand(pszCommand, cbCommand); 
    Unlock(); 
 
    return dwRetVal; 
} 
 
 
// This private method sends an AT command to the peer device 
DWORD CAGEngine::SendATCommand(LPSTR pszCommand, unsigned int cbCommand) 
{ 
    DWORD   dwRetVal = ERROR_SUCCESS; 
    DWORD   cdwBytesWritten = 0; 
 
#ifdef DEBUG 
    DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Data was sent: ")); 
    DbgPrintATCmd(ZONE_HANDLER, pszCommand, cbCommand); 
    DEBUGMSG(ZONE_HANDLER, (L"\n")); 
#endif // DEBUG 
 
    ASSERT(IsLocked()); 
 
    // 
    // This is the lowest layer in the send path for the AG.  At this point, we ensure 
    // we are not in sniff or park mode, and exit this mode if we indeed are.   
    // 
 
    EnsureActiveBaseband(); 
 
    // 
    // Send down the packet to RFCOMM 
    // 
 
    cdwBytesWritten = send(m_sockClient, pszCommand, cbCommand, 0); 
    if (SOCKET_ERROR == cdwBytesWritten) { 
        dwRetVal = GetLastError(); 
        DEBUGMSG(ZONE_ERROR, (_T("BTAGSVC: Error writing AT command to the device: %d\n"), dwRetVal)); 
    } 
    else if (cbCommand != cdwBytesWritten) { 
        DEBUGMSG(ZONE_WARN, (_T("BTAGSVC: Warning --> Send only wrote %d of %d total bytes.\n"), cdwBytesWritten, cbCommand)); 
    } 
 
    // 
    // Reschedule a thread to put us in sniff mode later 
    // 
 
    if (m_AGProps.ulSniffDelay && m_AGProps.fHandsfree && !m_AGProps.fPowerSave) { 
        // Sniff mode is enabled, we are connected to a hands-free, and we are persisting connections 
        if (m_SniffCookie) { 
            m_pTP->UnScheduleEvent(m_SniffCookie); 
            m_SniffCookie = NULL; 
        } 
 
        if ((m_AGProps.state >= AG_STATE_CONNECTED) && (m_AGProps.state < AG_STATE_AUDIO_UP)) { 
            m_SniffCookie = m_pTP->ScheduleEvent(EnterSniffModeThread, (LPVOID)this, m_AGProps.ulSniffDelay); 
        } 
    } 
 
    return dwRetVal; 
} 
 
 
// This method is used to call UI-related functions using a seperate thread. 
void CAGEngine::AGUIThread_Int(void) 
{ 
    BOOL fCallAG; 
 
    Lock(); 
 
    BOOL fHandsfree = m_AGProps.fHandsfree; 
 
    AddRef(); 
     
    if (AG_CALL_TYPE_IN == m_AGProps.usCallType) { 
        Unlock(); 
        fCallAG = BthAGOverrideCallIn(fHandsfree); 
        Lock(); 
    } 
    else { 
        Unlock(); 
        fCallAG = BthAGOverrideCallOut(fHandsfree); 
        Lock(); 
    } 
 
    DelRef(); 
 
    if (! fCallAG) { 
        CloseAudioChannel(); 
        if (m_AGProps.fPowerSave || !m_AGProps.fHandsfree) { 
            CloseControlChannel(); 
        } 
    } 
 
    CloseHandle(m_hUIThread); 
    m_hUIThread = NULL; 
 
    Unlock(); 
} 
 
 
// This thread is created to call UI-related functions that may need to block 
DWORD WINAPI CAGEngine::AGUIThread(LPVOID pv) 
{ 
    CAGEngine* pInst = (CAGEngine*)pv; 
    pInst->AGUIThread_Int(); 
    return 0; 
} 
 
void CAGEngine::TimeoutConnection_Int(void) 
{ 
    Lock(); 
 
    if (m_fCancelCloseConnection) { 
        goto exit; 
    } 
     
    CloseAudioChannel(); 
 
    m_AGProps.state = AG_STATE_CONNECTED; 
     
    if (m_AGProps.fPowerSave || !m_AGProps.fHandsfree) { 
        CloseControlChannel(); 
    }    
 
exit: 
    Unlock(); 
    return; 
} 
 
DWORD WINAPI CAGEngine::TimeoutConnection(LPVOID pv) 
{ 
    CAGEngine* pInst = (CAGEngine*)pv; 
    pInst->TimeoutConnection_Int(); 
    return 0; 
} 
 
void CAGEngine::EnterSniffModeThread_Int(void) 
{ 
    Lock(); 
 
    if ((m_AGProps.state >= AG_STATE_CONNECTED) && (m_AGProps.state < AG_STATE_AUDIO_UP)) { 
        unsigned char mode; 
         
        if (ERROR_SUCCESS == BthGetCurrentMode(&m_AGProps.btAddrClient, &mode)) { 
            if (mode < 0x02) { // check we are not already in deep enough sleep 
                DEBUGMSG(ZONE_HANDLER, (L"BTAGSVC: Going into SNIFF.\n"));  
                unsigned short usInt = 0; 
                if (ERROR_SUCCESS != BthEnterSniffMode(&m_AGProps.btAddrClient,  
                                        m_AGProps.usSniffMax,  
                                        m_AGProps.usSniffMin,  
                                        m_AGProps.usSniffAttempt,  
                                        m_AGProps.usSniffTimeout,  
                                        &usInt)) { 
                    DEBUGMSG(ZONE_WARN, (L"BTAGSVC: !! Warning - failed to enter sniff mode.\n")); 
                } 
            } 
        } 
    } 
 
    m_SniffCookie = NULL; 
 
    Unlock(); 
} 
 
DWORD WINAPI CAGEngine::EnterSniffModeThread(LPVOID pv) 
{ 
    CAGEngine* pInst = (CAGEngine*)pv; 
    pInst->EnterSniffModeThread_Int(); 
    return 0; 
} 
 
 
// 
// This function is called from the Network module 
// 
 
CAGEngine* g_pAGEngine; 
 
void BthAGOnNetworkEvent(DWORD dwEvent, LPVOID lpvParam, DWORD cbParam) 
{ 
    g_pAGEngine->OnNetworkEvent(dwEvent, lpvParam, cbParam); 
} 
 
 
// 
// This function is called from the PhoneExt module 
// 
 
void PhoneExtServiceCallback(BOOL fHaveService) 
{ 
    g_pAGEngine->OnServiceCallback(fHaveService); 
}