// Copyright (c) 2007-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
//

/**
 @file
 @internalComponent
*/

#include "eirmanager.h"
#include <bluetooth/hcicommandqueue.h>
#include <bttypes.h>
#include <bluetooth/hci/commandcompleteevent.h>
#include <bluetooth/hci/commandstatusevent.h>
#include <bluetooth/hci/command.h>
#include <bluetooth/hci/hciconsts.h>
#include <bluetooth/hci/writeextendedinquiryresponsecommand.h>
#include <bluetooth/logger.h>
#include "eirmansession.h"
#include "linkmgr.h"
#include "eirmanpanics.h"

#ifdef __FLOG_ACTIVE
_LIT8(KLogComponent, LOG_COMPONENT_EIRMANAGER);
#endif

__ASSERT_COMPILE(KReservedNameBytes + KReservedUuid16Bytes + KReservedUuid128Bytes + KReservedManufacturerSpecificDataBytes + KReservedTxPowerLevelBytes == KEirPacketSize);

CEirManager* CEirManager::NewL(MHCICommandQueue& aCommandQueue, CLinkMgrProtocol& aLinkMgrProtocol)
	{
	LOG_STATIC_FUNC
	
	CEirManager* self = new(ELeave) CEirManager(aCommandQueue, aLinkMgrProtocol);
	CleanupStack::PushL(self);
	self->ConstructL();
	CleanupStack::Pop(self);
	return self;
	}

CEirManager::~CEirManager()
	{
	LOG_FUNC
	
	// Delete all registered tags
	for(TInt8 i = static_cast<TInt8>(EEirTagName); i < static_cast<TInt8>(EEirTagRESERVED); i++)
		{
		__ASSERT_DEBUG(!iNotifiees[i].iNotifiee, EIR_MANAGER_PANIC(EEirManagerNotifieeNotCleared));
		}
	
	iCommandQueue.MhcqRemoveAllCommands(*this);
	delete iWriteEirTimer;
	}

CEirManager::CEirManager(MHCICommandQueue& aCommandQueue, CLinkMgrProtocol& aLinkMgrProtocol)
	: iOffersStale(EFalse)
	, iCmdId(KInvalidCommandQueueItemId)
	, iCmdCount(0)
	, iCommandQueue(aCommandQueue)
	, iLinkMgrProtocol(aLinkMgrProtocol)
	{
	LOG_FUNC
	}

void CEirManager::ConstructL()
	{
	LOG_FUNC
	iWriteEirTimer = CWriteEirTimer::NewL(*this);
	}

void CEirManager::UnhookClient(TEirTag aTag)
	{
	LOG_FUNC
	if (iNotifiees[aTag].iNotifiee)
		{
		iNotifiees[aTag].iNotifiee = NULL;
		if(iNotifiees[aTag].iTagData)
			{
			delete iNotifiees[aTag].iTagData;
			iNotifiees[aTag].iTagData = NULL;
			}			
		}
	}

// A client register aTag with EirManager
TInt CEirManager::RegisterTag(TEirTag aTag, MEirManagerNotifiee& aNotifiee)
	{
	LOG_FUNC
	TInt ret = KErrNone;
	LOG1(_L("CEirManager::RegisterTag tag = %d"), aTag);

	if(!TagValid(aTag))
		{
		LOG1(_L("CEirManager::RegisterTag INVALID TAG: %d"), aTag);
		ret = KErrArgument;
		}
	else if(!TagAllowed(aTag))
		{
		LOG(_L("CEirManager::RegisterTag: IN USE"));
		ret = KErrInUse;
		}
	else
		{
		iNotifiees[aTag].iNotifiee = &aNotifiee;
		iNotifiees[aTag].iTagData = NULL;
		iNotifiees[aTag].iOfferedBytes = 0;
		iNotifiees[aTag].iEirDataMode = EEirDataComplete;
		iNotifiees[aTag].iState = 0x00;

		if (!OffersPending() && NeedToOffer())
			{
			// Do not consider this for insertion yet if there are offers pending.
			// Instead, once the offers have been responded to, this client will be dealt with
			NotifyClients();
			}
		}
	return ret;
	}

// A client deregister aTag with EirManager
void CEirManager::DeregisterTag(TEirTag aTag)
	{
	LOG_FUNC
	LOG1(_L("CEirManager::DeregisterTag tag = %d"), aTag);

	if(!TagValid(aTag))
		{
		LOG1(_L("CEirManager::DeregisterTag INVALID TAG: %d"), aTag);
		return;
		}

	if (iNotifiees[aTag].iNotifiee)
		{
		UnhookClient(aTag);	
		if(iNotifiees[aTag].iRequiredBytes > 0)
			{
			// It did require some bytes and now requires none, reevaluate offers
			iNotifiees[aTag].iState &= ~KNeedOffer;
			// A client could storm in a deregister before setting data - so we remove
			// any pending offer as a SetData after deregistering will be errored - any
			// previous offer has been voided.
			iNotifiees[aTag].iState &= ~KOfferPending;
			if(!OffersPending())
				{
				NotifyClients();
				}
			else if(iNotifiees[aTag].iOfferedBytes > 0)
				{
				iOffersStale = ETrue;
				}
			}
		if(!NeedToOffer() && !OffersPending())
			{
			TryToUpdateHostController();
			}
		// Now tidy up offer and requested values (they are no longer valid).
		iNotifiees[aTag].iRequiredBytes = 0;
		iNotifiees[aTag].iOfferedBytes = 0;
		}
	}

TInt CEirManager::SetData(TEirTag aTag, TDesC8& aData, TEirDataMode aEirDataMode)
	{
	LOG_FUNC
	LOG1(_L("CEirManager::SetData tag = %d"), aTag);
	
	if(!iLinkMgrProtocol.IsExtendedInquiryResponseSupportedLocally())
		{
		return KErrNotSupported;
		}

	if(!TagValid(aTag))
		{
		LOG1(_L("CEirManager::SetData INVALID TAG: %d"), aTag);
		return KErrArgument;
		}

	if(!iNotifiees[aTag].iNotifiee)
		{
		// No client registered with this tag
		return KErrNotFound;
		}

	LOG2(_L("CEirManager::SetData data len = %d offered bytes: %d"), aData.Length(), iNotifiees[aTag].iOfferedBytes);

	if (!(iNotifiees[aTag].iState & KOfferPending))
		{
		// No offer pending for this tag
		return KErrNotReady;		
		}
	if(iNotifiees[aTag].iOfferedBytes < aData.Length())
		{
		// Client wants to publish more data than allowed to
		return KErrTooBig;
		}
		
	TInt ret = KErrNone;
	
	HBufC8** tagdata;
	
	FindTagData(aTag, tagdata);
	
	// Free up whatever there was before
	if (*tagdata)
		{
		delete *tagdata;
		}

	HBufC8* data = NULL;

	// If the descriptor is empty, mark the heap buffer pointer as NULL, 
	// meaning that the client is registered for this tag, but there is no currently no data available for it
	if(aData.Length() != 0x0)
		{
		data = aData.Alloc();
		if (!data)
			{
			LOG(_L("CEirManager::SetData: NO MEMORY "));
			ret = KErrNoMemory;
			}
		}

	*tagdata = data;

	// We no longer have a value pending.
	iNotifiees[aTag].iState &= ~KOfferPending;
	iNotifiees[aTag].iEirDataMode = aEirDataMode;

	if(NeedToOffer() && !OffersPending())
		{
		NotifyClients();
		}

	if(!NeedToOffer() && !OffersPending())
		{

		// Regardless of whether the allocation failed or not, check whether all offers have been accepted
		// and update the Host Controller accordingly
		TryToUpdateHostController();
		}
		
	LOG1(_L("CEirManager::SetData returning = %d"), ret);
	return ret;
	}

void CEirManager::MhcqcCommandEventReceived(const THCIEventBase& aEvent, 
											const CHCICommandBase* /*aRelatedCommand*/)
	{
	LOG_FUNC
	if(aEvent.EventCode() == ECommandCompleteEvent)
		{
		const THCICommandCompleteEvent& completeEvent = static_cast<const THCICommandCompleteEvent&>(aEvent);
		if(completeEvent.CommandOpcode() == KWriteExtendedInquiryResponseOpcode)
			{
			--iCmdCount;
			}
		else
			{
			LOG1(_L("Warning!! Unexpected Command Complete Event Received (command opcode: 0x%04x)"), completeEvent.CommandOpcode());
			__ASSERT_DEBUG(EFalse, EIR_MANAGER_PANIC(EEirManagerUnexpectedCompleteEvent));
			}
		}
	else
		{
		LOG1(_L("Warning!! Unexpected Event Received (event code: 0x%02x)"), aEvent.EventCode());
		__ASSERT_DEBUG(EFalse, EIR_MANAGER_PANIC(EEirManagerUnexpectedEvent));
		}
	}

void CEirManager::MhcqcCommandErrored(TInt aErrorCode, const CHCICommandBase* aCommand)
	{
	LOG_FUNC
	aErrorCode = aErrorCode;   // Avoid unused warning
	LOG1(_L("\taErrorCode = %d"), aErrorCode);
	if(aCommand && aCommand->Opcode() == KWriteExtendedInquiryResponseOpcode)
		{
		--iCmdCount;
		}
	else
		{
		LOG(_L("Warning!! Unexpected Error Received"));
		if(aCommand)
			{
			LOG1(_L("\taCommand->Opcode() = 0x%04x"), aCommand->Opcode());
			}
		}
	}

void CEirManager::WriteExtendedInquiryResponseL(TBool aFECRequired, const TDesC8& aEir)
	{
	LOG_FUNC
	if(iCmdCount > 0)
		{
		// There is at least 1 outstanding command in the queue
		TInt err = iCommandQueue.MhcqRemoveCommand(iCmdId, *this);
		if(err == KErrNone)
			{
			--iCmdCount;
			}
		// else if there was an error it is because the command cannot be removed
		// "at this time" so we just "leak" it.  This isn't a problem, this was a 
		// best effort optimisation.
		}
	CWriteExtendedInquiryResponseCommand* cmd = CWriteExtendedInquiryResponseCommand::NewL(aFECRequired ? 1 : 0, aEir);
	// Ownership of cmd transfered
	iCmdId = iCommandQueue.MhcqAddCommandL(cmd, *this);
	++iCmdCount;
	}

// Check whether a specific tag is allowed to be registered
TBool CEirManager::TagAllowed(TEirTag aTag) const
	{
	LOG_FUNC
	// We don't support 32 bit UUIDs
	return (aTag!=EEirTagSdpUuid32);
	}
	
// Check whether aTag is present
TBool CEirManager::TagPresent(TEirTag aTag) const
	{
	LOG_FUNC
	return (iNotifiees[aTag].iNotifiee != NULL);
	}

// Find the registered data for aTag
void CEirManager::FindTagData(TEirTag aTag, HBufC8**& aOutPointerToData)
	{
	LOG_FUNC
	aOutPointerToData = &iNotifiees[aTag].iTagData;
	}

// Count the number of registered tags
TInt CEirManager::TagCount() const
	{
	LOG_FUNC
	TInt count = 0;
	for (TInt i=0; i<EEirTagRESERVED; i++)
		{
		if (iNotifiees[i].iNotifiee)
			{
			count++;
			}
		}
	return count;
	}

TBool CEirManager::OffersPending() const
	{
	LOG_FUNC
	for (TInt i=0; i<EEirTagRESERVED; i++)
		{
		if(iNotifiees[i].iState & KOfferPending)
			{
			return ETrue;
			}
		}
	return EFalse;
	}

TBool CEirManager::NeedToOffer() const
	{
	LOG_FUNC
	if(iOffersStale)
		{
		return ETrue;
		}
	for (TInt i=0; i<EEirTagRESERVED; i++)
		{
		if (iNotifiees[i].iState & KNeedOffer)
			{
			return ETrue;
			}
		}
	return EFalse;
	}

// Notify EirManager there is a change of data from aTag client
TInt CEirManager::NewData(TEirTag aTag, TInt aRequiredBytes)
	{
	LOG_FUNC
	if(!iLinkMgrProtocol.IsExtendedInquiryResponseSupportedLocally())
		{
		return KErrNotSupported;
		}

	// including 2 bytes for length and tag
	TInt requiredBytes = 0;
	TInt currentBytes = iNotifiees[aTag].iRequiredBytes;
	if(aRequiredBytes > 0)
		{
		requiredBytes = aRequiredBytes + KEirLengthTagLength;
		}
	else if(aTag == EEirTagName && aRequiredBytes == 0)
		{
		// This is a special case for device without local name
		requiredBytes = KEirLengthTagLength;
		}
	else
		{
		requiredBytes = 0;
		}
	
	iNotifiees[aTag].iRequiredBytes = requiredBytes; // always record what we last asked for.
	
	if(!(iNotifiees[aTag].iState & KNeedOffer))
		{
		// We currently aren't marked as needing an offer so we will need to fix that...
		iNotifiees[aTag].iState |= KNeedOffer;
		if(requiredBytes == currentBytes && !(iNotifiees[aTag].iState & KOfferPending) && !iOffersStale)
			{
			// If the same then we handle locally, if we aren't already waiting.
			DoNotify(aTag);
			}
		else if(!OffersPending())
			{
			// If not waiting for anybody then lets kick off a round.
			NotifyClients();
			}
		}
	return KErrNone;
	}
	
// Notify all registered clients about their allocated data length
void CEirManager::NotifyClients()
	{

	LOG_FUNC

	// go around each notifiee and ask for values
	// our policy at the moment is: 
	// after allocating reserved bytes for each tag, we will prioritize tag with big granularity

	ComputeOfferLengths(); 

	for (TInt i = 0; i<EEirTagRESERVED; i++)
		{
		if(TagPresent((TEirTag) i))
			{
			DoNotify((TEirTag)i);
			}
		}
	}

// Notify each tag about the available data length
void CEirManager::DoNotify(TEirTag aTag)
	{
	LOG_FUNC

	iNotifiees[aTag].iState &= ~KNeedOffer; // We got an offer!
	
	// Only make valid offers: offers more than 0 and 0 offers with non 0 request
	if(iNotifiees[aTag].iOfferedBytes > 0)
		{
		// Notify client about offered bytes
		iNotifiees[aTag].iState |= KOfferPending;
		iNotifiees[aTag].iNotifiee->MemnEirBlockAvailable(aTag, iNotifiees[aTag].iOfferedBytes);
		}
	else if(iNotifiees[aTag].iOfferedBytes == 0)
		{
		if(iNotifiees[aTag].iRequiredBytes > 0)
			{
			// Notify client about 0 offer and not expecting client to call back
			iNotifiees[aTag].iState |= KOfferPending;
			iNotifiees[aTag].iNotifiee->MemnEirBlockAvailable(aTag, iNotifiees[aTag].iOfferedBytes);
			}
		else if(iNotifiees[aTag].iRequiredBytes == 0)
			{
			// request 0 and offered 0, delete client
			iNotifiees[aTag].iState &= ~KNeedOffer;
			delete iNotifiees[aTag].iTagData;
			iNotifiees[aTag].iTagData = NULL;
			}
		}
	}

void CEirManager::ComputeOfferLengths()
	{
	LOG_FUNC
	// count bytes available for EIR data, including length and tag
	// due to the low cost of Tx Power Level, it is always given a space in EIR
	TInt availableEirSize = EIRPacketSize();
	TInt reservedByteForName = KReservedNameBytes - (KEirPacketSize - availableEirSize);
	TInt totalSpare = 0;

	ComputeClientInitialOffer(EEirTagName, totalSpare, reservedByteForName);
	ComputeClientInitialOffer(EEirTagSdpUuid16, totalSpare, KReservedUuid16Bytes);
	ComputeClientInitialOffer(EEirTagSdpUuid128, totalSpare, KReservedUuid128Bytes);
	ComputeClientInitialOffer(EEirTagManufacturerSpecific, totalSpare, KReservedManufacturerSpecificDataBytes, ETrue);
	ComputeClientInitialOffer(EEirTagTxPowerLevel, totalSpare, KReservedTxPowerLevelBytes, ETrue);

	// if there is still bytes left after allocating the reserved bytes, 
	// we will try to allocate them by giving high priority to tag with bigger granularity
	/* tag			granularity (byte)
	   name				1
	  uuid16			2
	  uuid128			16
	  manufacturer		length
	*/
	LOG4(_L("RequiredBytes **** Name: %d, UUID16: %d, UUID128: %d, Manu: %d"), 
			iNotifiees[0].iRequiredBytes, iNotifiees[1].iRequiredBytes, iNotifiees[3].iRequiredBytes, iNotifiees[4].iRequiredBytes);
	LOG4(_L("OfferedBytes ----- Name: %d, UUID16: %d, UUID128: %d, Manu: %d"), 
			iNotifiees[0].iOfferedBytes, iNotifiees[1].iOfferedBytes, iNotifiees[3].iOfferedBytes, iNotifiees[4].iOfferedBytes);
	LOG1(_L("totalSpare: %d"), totalSpare);
	ComputeClientFinalOffer(EEirTagManufacturerSpecific, totalSpare, KReservedManufacturerSpecificDataBytes, ETrue);
	ComputeClientFinalOffer(EEirTagSdpUuid128, totalSpare, K128BitUuidGranularity);
	ComputeClientFinalOffer(EEirTagSdpUuid16, totalSpare, K16BitUuidGranularity);
	ComputeClientFinalOffer(EEirTagName, totalSpare, KDeviceNameGranularity);
	// Tx Power Level should be after the allocating the reserved bytes
	iOffersStale = EFalse; // We now know any "staleness" has been corrected.

	LOG(_L("After allocating reserved"));
	LOG4(_L("RequiredBytes **** Name: %d, UUID16: %d, UUID128: %d, Manu: %d"),
			iNotifiees[0].iRequiredBytes, iNotifiees[1].iRequiredBytes, iNotifiees[3].iRequiredBytes, iNotifiees[4].iRequiredBytes);
	LOG4(_L("OfferedBytes ----- Name: %d, UUID16: %d, UUID128: %d, Manu: %d"),
			iNotifiees[0].iOfferedBytes, iNotifiees[1].iOfferedBytes, iNotifiees[3].iOfferedBytes, iNotifiees[4].iOfferedBytes);
	LOG1(_L("totalSpare: %d"), totalSpare);
	}

/* compute offer based on client's request and the reserved bytes for this client*/
void CEirManager::ComputeClientInitialOffer(TEirTag aTag, TInt& aTotalSpare, TInt aReservedBytes,
											TBool aIsAllOrNothing)
	{
	if(TagPresent(aTag))
		{
		if(aIsAllOrNothing)
			{
			if(iNotifiees[aTag].iRequiredBytes <= aReservedBytes)
				{
				iNotifiees[aTag].iOfferedBytes = iNotifiees[aTag].iRequiredBytes;
				aTotalSpare += (aReservedBytes - iNotifiees[aTag].iRequiredBytes);
				iNotifiees[aTag].iEirDataMode = EEirDataComplete;
				}
			else
				{
				// the request exceeds reserved bytes for this client, the client is offered nothing
				iNotifiees[aTag].iOfferedBytes = 0;
				iNotifiees[aTag].iEirDataMode = EEirDataPartial;
				aTotalSpare += aReservedBytes;
				}
			}
		else
			{
			if(iNotifiees[aTag].iRequiredBytes <= aReservedBytes)
				{
				iNotifiees[aTag].iOfferedBytes = iNotifiees[aTag].iRequiredBytes;
				aTotalSpare += (aReservedBytes - iNotifiees[aTag].iRequiredBytes);
				iNotifiees[aTag].iEirDataMode = EEirDataComplete;
				}
			else
				{
				iNotifiees[aTag].iOfferedBytes = aReservedBytes;
				iNotifiees[aTag].iEirDataMode = EEirDataPartial;
				}
			}
		}
	else
		{
		aTotalSpare += aReservedBytes;
		}
	}

/* compute offer based on client's request and the spared bytes from the initial offers*/
void CEirManager::ComputeClientFinalOffer(TEirTag aTag, TInt& aTotalSpare, TUint aGranularity,
				  TBool aIsAllOrNothing)
	{
	if(iNotifiees[aTag].iRequiredBytes > iNotifiees[aTag].iOfferedBytes)
		{
		if(aIsAllOrNothing)
			{
			// this means we have more data to publish, which also indicates
			// it required bytes are more than reserved bytes for this tag
			if(iNotifiees[aTag].iRequiredBytes <= aTotalSpare)
				{
				aTotalSpare -= iNotifiees[aTag].iRequiredBytes;
				iNotifiees[aTag].iOfferedBytes = iNotifiees[aTag].iRequiredBytes;
				iNotifiees[aTag].iEirDataMode = EEirDataComplete;
				}
			}
		else
			{
			// this means we have more data to publish
			if((iNotifiees[aTag].iRequiredBytes - iNotifiees[aTag].iOfferedBytes) <= aTotalSpare)
				{
				aTotalSpare -= (iNotifiees[aTag].iRequiredBytes - iNotifiees[aTag].iOfferedBytes);
				iNotifiees[aTag].iOfferedBytes = iNotifiees[aTag].iRequiredBytes;
				iNotifiees[aTag].iEirDataMode = EEirDataComplete;
				}
			else
				{
				iNotifiees[aTag].iOfferedBytes += (aTotalSpare - aTotalSpare % aGranularity);
				aTotalSpare %= aGranularity;
				}
			}
		}
	}

// Calculate the valid size of EIR packet, excluding payload header	
TInt CEirManager::EIRPacketSize() const
	{
	LOG_FUNC
	TInt total = 0;
	// default value of valid EIR packet is for using DM1
	TInt ret = KEirPacketSize - KEirDM1PayloadHeaderSize;
	for (TInt i = 0; i<EEirTagRESERVED; i++)
		{
		if(TagPresent((TEirTag) i) && iNotifiees[i].iRequiredBytes > 0)
			{
			total += iNotifiees[i].iRequiredBytes;
			// Including 2 bytes for the length and tag
			total += KEirLengthTagLength;
			}
		}
		
	if(total > KEirDM1PacketSize)
		{
		// None DM1 packet has 2 bytes of payload header, recalculate...
		ret = KEirPacketSize - KEirNoneDM1PayloadHeaderSize;
		}

	return ret;
	}

void CEirManager::UpdateHostControllerL()
	{
	LOG_FUNC
	
	/* We don't handle the situation of handware swapping after stack is started
	 * when the device changed from 2.1 to 2.0 and EIR is no longer supports, 
	 * we'll quietly stop writing EIR and return KErrNotSupported for future requests.
	we
	 */
	__ASSERT_DEBUG(iLinkMgrProtocol.IsExtendedInquiryResponseSupportedLocally(), EIR_MANAGER_PANIC(EEirManagerEirNotSupported));
	TBuf8<KHCIExtendedInquiryResponseMaxLength> eir;

	for (TInt i = 0; i<EEirTagRESERVED; i++)
		{
		TExtendedInquiryResponseDataType dataType = EEirLocalNamePartial;

		// Skip non-registered and data-empty tags
		if(iNotifiees[i].iNotifiee)
			{
			if(i == EEirTagName || iNotifiees[i].iTagData)
				{
				// We'll always try to publish empty device name according to the spec
				switch (i)
					{
					// Name should go first
					case EEirTagName:
					dataType = iNotifiees[i].iEirDataMode == EEirDataPartial ? EEirLocalNamePartial : EEirLocalNameComplete;
					break;
					
					case EEirTagSdpUuid16:
					dataType = iNotifiees[i].iEirDataMode == EEirDataPartial ?  EEirUUID16Partial : EEirUUID16Complete;
					break;
									
					case EEirTagSdpUuid128:
					dataType = iNotifiees[i].iEirDataMode == EEirDataPartial ?  EEirUUID128Partial : EEirUUID128Complete;
					break;
					
					case EEirTagManufacturerSpecific:
					dataType = EEirVendorSpecific;
					break;
					
					case EEirTagTxPowerLevel:
					dataType = EEirTxPowerLevel;
					break;
					}
				if(iNotifiees[i].iTagData)
					{
					// Data Structure length, add a byte for the Data Type
					eir.Append(iNotifiees[i].iTagData->Length() + 1);
					// Data Structure data: Data Type
					eir.Append(dataType);
					// Data Structure data: EIR data
					eir.Append(*(iNotifiees[i].iTagData));
					}
				else
					{
					// Here is the case of empty device name again
					eir.Append(1);
					// Data Structure data: Data Type
					eir.Append(dataType);
					}
				}
			}
		}

	LOG1(_L("CEirManager::UpdateHostController about to send %d bytes of EIR data"), eir.Length());
	// Send this down HCI
	WriteExtendedInquiryResponseL(ETrue, eir);

	LOG(_L("------- EIR verbose display"));
	LOGHEXDESC(eir);
	}

void CEirManager::TryToUpdateHostController()
	{
	// In case there is an outstanding write eir command (a timer running) already, Cancel()
	iWriteEirTimer->Cancel();
	
	// Start the timer to gurad against from clients' updates flooding the controller
	iWriteEirTimer->Start();
	}


CWriteEirTimer* CWriteEirTimer::NewL(CEirManager& aEirManager)
	{
	CWriteEirTimer* timer = new (ELeave) CWriteEirTimer(aEirManager);
	CleanupStack::PushL(timer);
	timer->ConstructL();
	CleanupStack::Pop(timer);
	return timer;
	}


CWriteEirTimer::CWriteEirTimer(CEirManager& aEirManager)
: CTimer(EPriorityStandard), iEirManager(aEirManager)
	{
	}

void CWriteEirTimer::Start()
	{
	After(KWriteEirTimeout);
	}

void CWriteEirTimer::ConstructL()
	{
	CTimer::ConstructL();
	CActiveScheduler::Add(this);
	}

void CWriteEirTimer::RunL()
	{
	// timer expires, which means no more write eir request within life of the time (1 second)
	// write eir here now
	iEirManager.UpdateHostControllerL();
	}

TInt CWriteEirTimer::RunError(TInt IF_FLOGGING(aError))
	{
	LOG_FUNC
	// The only reason the error occurs is that the WriteExtendedInquiryResponseL has failed
	// This may be caused by invalid EIR data, we'll do nothing here.
	LOG1(_L("CWriteEirTimer::RunError() %d: "), aError);
	return KErrNone;
	}
