diff -r 000000000000 -r f63038272f30 bluetoothappprofiles/avrcp/remconbeareravrcp/src/avrcpoutgoingcommandhandler.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bluetoothappprofiles/avrcp/remconbeareravrcp/src/avrcpoutgoingcommandhandler.cpp Mon Jan 18 20:28:57 2010 +0200 @@ -0,0 +1,768 @@ +// Copyright (c) 2004-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 + @released +*/ + +#include + +#include "avcpanel.h" +#include "controlcommand.h" +#include "avrcpoutgoingcommandhandler.h" +#include "avrcplog.h" +#include "avrcprouter.h" +#include "avrcptimer.h" +#include "avrcputils.h" +#include "controlbearer.h" + +//--------------------------------------------------------------------- +// Construction/Destruction +//--------------------------------------------------------------------- + +/** Factory function. + +@param aBearer The CRemConBearerAvrcp this is to handle commands for. +@param aObserver The observer of the bearer. Used to aquire converters. +@param aRouter A CRcpRouter to use for communication with remote devices. +@param aTimer CDeltaTimer to use for queuing timed events. +@return A fully constructed CRcpOutgoingCommandHandler. +@leave System wide error codes. +*/ +CRcpOutgoingCommandHandler* CRcpOutgoingCommandHandler::NewL(CRemConBearerAvrcp& aBearer, + MRemConBearerObserver& aObserver, + CRcpRouter& aRouter, + CDeltaTimer& aTimer) + { + LOG_STATIC_FUNC + CRcpOutgoingCommandHandler* handler = new(ELeave)CRcpOutgoingCommandHandler(aBearer, aObserver, aRouter, aTimer); + return handler; + } + +/** Constructor. + +@param aBearer The CRemConBearerAvrcp this is to handle commands for. +@param aObserver The observer of the bearer. Used to aquire converters. +@param aRouter A CRcpRouter to use for communication with remote devices. +@param aTimer CDeltaTimer to use for queuing timed events. +@return A partially constructed CRcpIncomingCommandHandler. +@leave System wide error codes. +*/ +CRcpOutgoingCommandHandler::CRcpOutgoingCommandHandler(CRemConBearerAvrcp& aBearer, + MRemConBearerObserver& aObserver, + CRcpRouter& aRouter, + CDeltaTimer& aTimer) : iCommandQueue(_FOFF(CControlCommand, iHandlingLink)), + iNotifyCommandQueue(_FOFF(CControlCommand, iHandlingLink)), + iBearer(aBearer), iObserver(aObserver), iRouter(aRouter), iTimer(aTimer) + { + LOG_FUNC + } + +/** Destructor. +*/ +CRcpOutgoingCommandHandler::~CRcpOutgoingCommandHandler() + { + LOG_FUNC + + ClearQueue(iCommandQueue); + ClearQueue(iNotifyCommandQueue); + } + +void CRcpOutgoingCommandHandler::ClearQueue(TDblQue& aQue) + { + while(!aQue.IsEmpty()) + { + CControlCommand* command = aQue.First(); + command->CancelTimer(iTimer); + command->iHandlingLink.Deque(); + command->DecrementUsers(); + } + } +//--------------------------------------------------------------------- +// Called from the bearer +//--------------------------------------------------------------------- + +/** Tell the handler to gracefully shutdown. + +@param aClearQueue Whether to clear the queue without handling the things + on it. If this is true the commands will be deleted. + If this is false then pending commands will have responses + generated to RemCon. +*/ +void CRcpOutgoingCommandHandler::Disconnect(TBool aClearQueue) + { + LOG_FUNC + ProcessDisconnect(iCommandQueue, aClearQueue); + ProcessDisconnect(iNotifyCommandQueue, aClearQueue); + } + +void CRcpOutgoingCommandHandler::ProcessDisconnect(TDblQue& aQue, TBool aClearQueue) + { + while(!aQue.IsEmpty()) + { + CControlCommand* command = aQue.First(); + iRouter.RemoveFromSendQueue(*command); + command->CancelTimer(iTimer); + + if(aClearQueue) + { + GenerateFailureResult(*command, KErrDisconnected); + } + + command->iHandlingLink.Deque(); + command->DecrementUsers(); + } + } +/** Sends a new command. + +@param aInterfaceUid The RemCon client interface this command is from. +@param aCommand The operation id within aInterfaceUid. +@param aId A unique identifier provided by RemCon. +@param aCommandData Data associated with this command. +@param aAddr Bluetooth address of device to send this command to. +@leave KErrNoMemory or system wide error code. +@leave Command parsing error. +*/ +void CRcpOutgoingCommandHandler::SendCommandL(TUid aInterfaceUid, + TUint aCommand, + TUint aId, + RBuf8& aCommandData, + const TBTDevAddr& aAddr) + { + LOG_FUNC + + if(aInterfaceUid.iUid == KRemConCoreApiUid) + { + // Passthrough commands are stateful, so we need to examine the + // history - we can't just blindly wham it on the queue. + HandleCoreApiCommandL(aCommand, aId, aCommandData, aAddr); + } + else + { + SendCommandL(aInterfaceUid, aCommand, aId, aCommandData, EFalse, aAddr, ETrue, EFalse); + } + } + +/** Sends a new notify command. + +@param aInterfaceUid The RemCon client interface this command is from. +@param aCommand The operation id within aInterfaceUid. +@param aId A unique identifier provided by RemCon, the transaction ID. +@param aCommandData Data associated with this command. +@param aAddr Bluetooth address of device to send this command to. +@leave KErrNoMemory or system wide error code. +@leave Command parsing error. +*/ +void CRcpOutgoingCommandHandler::SendNotifyCommandL(TUid aInterfaceUid, + TUint aCommand, + TUint aId, + RBuf8& aCommandData, + const TBTDevAddr& aAddr) + { + LOG_FUNC + SendCommandL(aInterfaceUid, aCommand, aId, aCommandData, EFalse, aAddr, ETrue, ETrue); + } + +//--------------------------------------------------------------------- +// Data notifications from the router +//--------------------------------------------------------------------- + +/** Called by the router to provide a new response. + +@param aFrame The AV/C frame for this response. Ownership is taken. +@param aTransLabel The AVCTP transaction id of this response. This is used + to match it with its command. +@param aAddr The remote from which this response originated +*/ +void CRcpOutgoingCommandHandler::ReceiveResponse(const TDesC8& aMessageInformation, + SymbianAvctp::TTransactionLabel aTransLabel, + TBool aIpidBitSet) + { + LOG_FUNC + + CAVCFrame* frame = NULL; + TInt err = KErrNone; + if(!aIpidBitSet) + { + TRAP(err, frame = CAVCFrame::NewL(aMessageInformation, AVC::EResponse)); + } + + if(!err) + { + CControlCommand* command = NULL; + command = FindInQueue(iCommandQueue, aTransLabel); + if ( command != NULL ) + { + //Found, so it is a normal command response. + ProcessReceiveResponse(frame, aIpidBitSet, command, EFalse); + } + else + { + //Try to find in the notify command queue. + command = FindInQueue(iNotifyCommandQueue, aTransLabel); + if( command != NULL ) + { + //Found, so it is a notify command response. + ProcessReceiveResponse(frame, aIpidBitSet, command, ETrue); + } + } + + delete frame; + } + } + +CControlCommand* CRcpOutgoingCommandHandler::FindInQueue(TDblQue& aQue, + SymbianAvctp::TTransactionLabel aTransLabel) + { + CControlCommand* command = NULL; + TDblQueIter iter(aQue); + while (iter) + { + command = iter++; + if(command->TransactionLabel() == aTransLabel) + { + return command; + } + } + + return NULL; + } + +void CRcpOutgoingCommandHandler::ProcessReceiveResponse(CAVCFrame* aFrame, + TBool aIpidBitSet, + CControlCommand* aCommand, + TBool aNotify) + { + aCommand->CancelTimer(iTimer); + + TInt err = KErrNone; + // Inform the bearer if this is something it knows about + // ie not a click release + if(aCommand->KnownToBearer()) + { + if(!aIpidBitSet) + { + if(aFrame->Data().Length() < KAVCFrameHeaderLength) + { + // Drop corrupt frames + return; + } + + err = aCommand->ParseIncomingResponse(iObserver, *aFrame); + } + else + { + // If aIpidBitSet is true that means AVRCP is not supported + // by the remote end. We handle this in the same way as not + // supported commands, passing them up to RemCon as not + // supported, so just map the ctype here, rather than setting + // up another path for ipid handling, but we need pass as the + // frame the original because we don't get one from AVCTP if + // ipid is set. + aCommand->SetResponseType(KErrNotSupported); + err = aCommand->ParseIncomingResponse(iObserver, aCommand->Frame()); + } + + if ( aNotify ) + {//This is a notify command + iBearer.MrccciNewNotifyResponse(*aCommand); + } + else + { + iBearer.MrccciNewResponse(*aCommand); + } + } + + TBool doDeque = ETrue; + if ( (!aIpidBitSet) && (err == KErrNone) && (aNotify) && (aFrame->Type() == AVC::EInterim)) + { + doDeque = EFalse; + } + + // If this a passthrough press that hasn't yet been released, we need + // to wait for a release before getting rid of this, otherwise we're done. + if(aCommand == iUnreleasedCommand) + { + iUnreleasedHasResponse = ETrue; + StartReleaseTimer(*iUnreleasedCommand); + doDeque = EFalse; + } + + if ( doDeque ) + { + aCommand->iHandlingLink.Deque(); + aCommand->DecrementUsers(); + } + } +/** Called by the router to complete a send. + +@param aCommand The command which has been sent. +@param aSendResult The result of the send. KErrNone if successful. +*/ +void CRcpOutgoingCommandHandler::MessageSent(CAvrcpCommand& aCommand, TInt aSendResult) + { + LOG_FUNC + + if(aSendResult == KErrNone) + { + // Set off response timer + StartResponseTimer(static_cast(aCommand)); + } + else + { + CControlCommand* command = FindInQueue(iNotifyCommandQueue, aCommand.TransactionLabel()); + + if(command) + { + command->SetNotifyVolumeChangeResult(command->Frame()); + iBearer.MrccciNewNotifyResponse(*command); + } + else + { + command = FindInQueue(iCommandQueue, aCommand.TransactionLabel()); + + // Generate error response up to RemCon + // if this is a core command we can set the result, + // otherwise we just return it as we got it. + if(command->Frame().Opcode() == AVC::EPassThrough) + { + // Need to insert before setting the button action so we have + // long enough data + if (!command->InsertCoreResult(aSendResult)) + { + if(command->Click()) + { + command->SetCoreButtonAction(ERemConCoreApiButtonClick, ETrue); + } + } + } + + iBearer.MrccciNewResponse(*command); + } + + command->iHandlingLink.Deque(); + command->DecrementUsers(); + } + } + +//--------------------------------------------------------------------- +// Internal Utility functions +//--------------------------------------------------------------------- + +void CRcpOutgoingCommandHandler::CleanupUnreleased() + { + iUnreleasedCommand->CancelTimer(iTimer); + iUnreleasedCommand->iHandlingLink.Deque(); + iUnreleasedCommand->DecrementUsers(); + iUnreleasedHasResponse = EFalse; + } + +/** Handle a command that is part of the Core API. + +@param aCommand The operation id within aInterfaceUid. +@param aId A unique identifier provided by RemCon. +@param aCommandData Data associated with this command. +@param aAddr Bluetooth address of device to send this command to. +@leave KErrNoMemory or system wide error code. +@leave Command parsing error. +*/ +void CRcpOutgoingCommandHandler::HandleCoreApiCommandL(TUint aCommand, + TUint aId, + RBuf8& aCommandData, + const TBTDevAddr& aAddr) + { + if(aCommandData.Length() < KRemConCoreApiButtonDataLength) + { + User::Leave(KErrCorrupt); + } + + TInt buttonData; + AvrcpUtils::ReadCommandDataToInt(aCommandData, + KRemConCoreApiButtonDataOffset, KRemConCoreApiButtonDataLength, buttonData); + + // First check if there's anything we need to do before sending this command, + // mainly releasing a previous press. + if(iUnreleasedCommand) + { + TUint prevOpId = iUnreleasedCommand->RemConOperationId(); + + if(aCommand == prevOpId) + { + // Either we've received a release, or we've refreshed the press. + // If the unreleased press has already been responded too we can + // dispose of it now. + // If the unreleased press has not been responded too we can + // treat it like a normal command on reception of response, so just + // set iUnreleased to NULL. + if(iUnreleasedHasResponse) + { + CleanupUnreleased(); + } + + iUnreleasedCommand = NULL; + } + else + { + // A new operation! + if(buttonData != ERemConCoreApiButtonRelease) + { + // Try and generate the release for the previous command, if + // if fails then the remote will just have to assume it. + // There's no point leaving this to the release timer, because + // we want to send another command now, so even if we send the + // release later the remote should ignore it. + TRAP_IGNORE(GenerateCommandL(*iUnreleasedCommand, ERemConCoreApiButtonRelease)); + + if(iUnreleasedHasResponse) + { + CleanupUnreleased(); + } + + iUnreleasedCommand = NULL; + } + else + { + // A release for a command other than iUnreleased. We can't + // send this now. + User::Leave(KErrNotReady); + } + } + } + else if(buttonData == ERemConCoreApiButtonRelease) + { + // We don't have an unreleased command. We must have already + // released this, either via the timer, or because we've sent + // another command in the meantime. We can't send this now. + // Leaving synchronously means we don't need to worry about generating + // a fake response, which may mislead the application. + User::Leave(KErrNotReady); + } + + if(buttonData == ERemConCoreApiButtonClick) + { + // aCommandData is still owned by RemCon until we return successfully. + // If we try the operations with the new data first we won't end up + // in a situation where the new CControlCommand thinks that it owns + // aCommandData, then the release operation leaves, so RemCon also + // thinks it owns aCommandData. + + RBuf8 pressBuf; + pressBuf.CreateL(aCommandData); + CleanupClosePushL(pressBuf); + + AvrcpUtils::SetCommandDataFromInt(pressBuf, + KRemConCoreApiButtonDataOffset, KRemConCoreApiButtonDataLength, ERemConCoreApiButtonPress); + SendCommandL(TUid::Uid(KRemConCoreApiUid), aCommand, aId, pressBuf, ETrue, aAddr, ETrue, EFalse); + + // Data has been taken ownership of by SendCommandL, so can just let + // pressbuf go out of scope. + CleanupStack::Pop(&pressBuf); + + AvrcpUtils::SetCommandDataFromInt(aCommandData, + KRemConCoreApiButtonDataOffset, KRemConCoreApiButtonDataLength, ERemConCoreApiButtonRelease); + SendCommandL(TUid::Uid(KRemConCoreApiUid), aCommand, aId, aCommandData, ETrue, aAddr, EFalse, EFalse); + } + else if(buttonData == ERemConCoreApiButtonPress) + { + iUnreleasedCommand = &SendCommandL(TUid::Uid(KRemConCoreApiUid), aCommand, aId, aCommandData, EFalse, aAddr, ETrue, EFalse); + iReleaseTimerExpiryCount = 0; + } + else + { + // Must be release + __ASSERT_DEBUG(buttonData == ERemConCoreApiButtonRelease, AvrcpUtils::Panic(EAvrcpUnknownButtonAction)); + SendCommandL(TUid::Uid(KRemConCoreApiUid), aCommand, aId, aCommandData, EFalse, aAddr, ETrue, EFalse); + } + } + +/** Creates a command from the RemCon data. + +This is an internal utility function. + +A CControlCommand will be created. Calling ProcessOutgoingCommandL on +this creates a CAVCFrame from the provided data. If an AV/C frame +can be created the command will be added to the outgoing queue to +wait a response from the remote. Otherwise this function will +leave. + +@param aInterfaceUid The RemCon client interface this command is from. +@param aCommand The operation id within aInterfaceUid. +@param aId A unique identifier provided by RemCon. +@param aCommandData Data associated with this command. +@param aIsClick Whether this is a button click. +@param aAddr Bluetooth address of device to send this command to. +@return The generated command. +@leave KErrNoMemory or system wide error code. +@leave Command parsing error. +*/ +CControlCommand& CRcpOutgoingCommandHandler::SendCommandL(TUid aInterfaceUid, + TUint aCommand, + TUint aId, + RBuf8& aCommandData, + TBool aIsClick, + const TBTDevAddr& aAddr, + TBool aKnownToBearer, + TBool aNotify) + { + LOG_FUNC + // Create a command and wham it on our queue, so we can match it up with its response + // CControlCommand::NewL takes ownership of the data in aCommandData then NULLs aCommandData + // so a leave later in the function won't cause double deletion. + CControlCommand* command = CControlCommand::NewL(aInterfaceUid, aCommand, aId, iCurrentTrans, + aCommandData, aIsClick, aAddr, aKnownToBearer); + CleanupStack::PushL(command); + + command->ProcessOutgoingCommandL(iObserver); + CleanupStack::Pop(command); + command->IncrementUsers(); + + if ( aNotify ) + { + iNotifyCommandQueue.AddLast(*command); + } + else + { + iCommandQueue.AddLast(*command); + } + + // Increment our transaction id + iCurrentTrans = (iCurrentTrans + 1) % SymbianAvctp::KMaxTransactionLabel; + + // Command stays on the queue till we've got the response + iRouter.AddToSendQueue(*command); + + return *command; + } + +/** Generate a failure response to RemCon. + +This sets the result for a passthrough command. +It informs the bearer of the new response. + +@param aCommand The command to finish off. +@param aResult The result (only valid for passthrough) +*/ +void CRcpOutgoingCommandHandler::GenerateFailureResult(CControlCommand& aCommand, TInt aResult) + { + // Response is only necessary if the bearer knows about this command. + if(aCommand.KnownToBearer() && (aCommand.Frame().Opcode() == AVC::EPassThrough)) + { + if (aCommand.InsertCoreResult(aResult) == KErrNone) + { + if(aCommand.Click()) + { + aCommand.SetCoreButtonAction(ERemConCoreApiButtonClick, ETrue); + } + else if(aCommand.ButtonAct() == AVCPanel::EButtonPress) + { + aCommand.SetCoreButtonAction(ERemConCoreApiButtonPress, ETrue); + } + else + { + aCommand.SetCoreButtonAction(ERemConCoreApiButtonRelease, ETrue); + } + + iBearer.MrccciNewResponse(aCommand); + } + } + } + +/** Generate a command to the remote. + +This is needed in situations where the application has not met the avrcp +button refresh requirements so we need to internally generate something +to stop the remote getting a bad impression of us. + +@param aCommand The command to be issue again. +*/ +void CRcpOutgoingCommandHandler::GenerateCommandL(CControlCommand& aCommand, TRemConCoreApiButtonAction aButtonAct) + { + LOG_FUNC + + RBuf8 commandData; + commandData.CreateMaxL(KRemConCoreApiButtonDataLength); + + AvrcpUtils::SetCommandDataFromInt(commandData, + KRemConCoreApiButtonDataOffset, KRemConCoreApiButtonDataLength, aButtonAct); + + // This will not leave before taking ownership of commandData. + SendCommandL(aCommand.RemConInterfaceUid(), aCommand.RemConOperationId(), aCommand.RemConCommandId(), commandData, EFalse, + aCommand.RemoteAddress(), EFalse, EFalse); + } + +//------------------------------------------------------------------------------------ +// Timer functions +//------------------------------------------------------------------------------------ + +/** Starts the response timer. + +AVRCP mandates a remote respond within 100ms of receiving a command. +This is the timer for that, and is started when a command has +successfully been sent. + +@param aCommand The command to start the timer for. +*/ +void CRcpOutgoingCommandHandler::StartResponseTimer(CControlCommand& aCommand) + { + LOG_FUNC + // These use placement new, so cannot fail + TAvrcpTimerExpiryInfo* timerInfo = new(aCommand.TimerExpiryInfo())TAvrcpTimerExpiryInfo(this, aCommand); + + TCallBack callback(ResponseExpiry, timerInfo); + TDeltaTimerEntry* timerEntry = new(aCommand.TimerEntry())TDeltaTimerEntry(callback); + + iTimer.Queue(KRcpResponseTimeOut, *timerEntry); + } + +/** Callback when response timer expires. + +This is a static forwarding function. + +@param aExpiryInfo The information used by the real ResponseExpiry to + deal with the timer expiry. +*/ +TInt CRcpOutgoingCommandHandler::ResponseExpiry(TAny* aExpiryInfo) + { + LOG_STATIC_FUNC + TAvrcpTimerExpiryInfo *timerInfo = reinterpret_cast(aExpiryInfo); + (reinterpret_cast(timerInfo->iHandler))->ResponseExpiry(timerInfo->iCommand); + + return KErrNone; + } + +/** Deals with response timeout. + +This sends a timeout response to RemCon. + +@param aCommand The CControlCommand that has expired. +*/ +void CRcpOutgoingCommandHandler::ResponseExpiry(CControlCommand& aCommand) + { + LOG_FUNC + + GenerateFailureResult(aCommand, KErrTimedOut); + + // Failed to get a response to this, don't bother about trying + // to release it. + if(iUnreleasedCommand == &aCommand) + { + iUnreleasedCommand = NULL; + __ASSERT_DEBUG(!iUnreleasedHasResponse, AvrcpUtils::Panic(EAvrcpPressHasPhantomResponse)); + } + + aCommand.iHandlingLink.Deque(); + aCommand.DecrementUsers(); + } + +/** Starts the release timer. + +AVRCP requires a press to be refreshed less than 2s after the first +press, if it is not to be assumed to have been released. We pass +this requirement on to RemCon clients as we don't know when they might +go away and we don't want to keep buttons pressed forever. If the +release timer expires we will assume a release, and generate it to +the remote. + +@param aCommand The command to start the timer for. +*/ +void CRcpOutgoingCommandHandler::StartReleaseTimer(CControlCommand& aCommand) + { + LOG_FUNC + + // These cannot fail as we use placement new + TAvrcpTimerExpiryInfo* timerInfo = new(aCommand.TimerExpiryInfo())TAvrcpTimerExpiryInfo(this, aCommand); + + TCallBack callback(ReleaseExpiry, timerInfo); + TDeltaTimerEntry* timerEntry = new(aCommand.TimerEntry())TDeltaTimerEntry(callback); + + iTimer.Queue(KRcpOutgoingButtonReleaseTimeout, *timerEntry); + } + +/** Callback when release timer expires. + +This is a static forwarding function. + +@param aExpiryInfo The information used by the real ReleaseExpiry to + deal with the timer expiry. +*/ +TInt CRcpOutgoingCommandHandler::ReleaseExpiry(TAny* aExpiryInfo) + { + LOG_STATIC_FUNC + TAvrcpTimerExpiryInfo *timerInfo = reinterpret_cast(aExpiryInfo); + (reinterpret_cast(timerInfo->iHandler))->ReleaseExpiry(timerInfo->iCommand); + + return KErrNone; + } + +/** Deals with expiry of release timer. + +1) Generate release for this command. +2) Send release to remote. + +@param aCommand The CControlCommand that has expired. +*/ +void CRcpOutgoingCommandHandler::ReleaseExpiry(CControlCommand& aCommand) + { + LOG_FUNC + __ASSERT_DEBUG((aCommand.ButtonAct() == AVCPanel::EButtonPress), AvrcpUtils::Panic(EAvrcpReleaseExpiryForRelease)); + __ASSERT_DEBUG(&aCommand == iUnreleasedCommand, AvrcpUtils::Panic(EAvrcpReleaseExpiryForOldCommand)); + __ASSERT_DEBUG(iUnreleasedHasResponse, AvrcpUtils::Panic(EAvrcpReleaseTimerStartedWithoutResponse)); + + iReleaseTimerExpiryCount++; + + TBool commandCompleted = ETrue; + // If the client is not yet obliged to refresh this, then send another press. Otherwise generate + // the release for them. + if((iReleaseTimerExpiryCount * KRcpOutgoingButtonReleaseTimeout) < KRemConCoreApiPressRefreshInterval) + { + // This will try and generate a press that is identical to the original + // aCommand, but with a new AVCTP transaction id. + TRAPD(err, GenerateCommandL(aCommand, ERemConCoreApiButtonPress)); + + if(!err) + { + // Start the timer again on the original command + StartReleaseTimer(aCommand); + commandCompleted = EFalse; + } + } + else + { + // Try an generate a release, but if it fails we just have to let the + // remote assume it. + TRAP_IGNORE(GenerateCommandL(aCommand, ERemConCoreApiButtonRelease)); + } + + // This condition may be true because + // - we have failed to generate a press, in which case the remote is entitled + // to assume this is released, so we just give up on it. + // or + // - the client has not met its press refresh obligation (whether we've + // successfully generated a release or not. + // In either case we won't do anything more with this command. + if(commandCompleted) + { + aCommand.iHandlingLink.Deque(); + aCommand.DecrementUsers(); + + iUnreleasedCommand = NULL; + iUnreleasedHasResponse = EFalse; + iReleaseTimerExpiryCount = 0; + } + }