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); }