bluetoothappprofiles/avrcp/remconbeareravrcp/src/passthroughhelper.cpp
changeset 0 f63038272f30
child 20 2f88a7d66f50
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bluetoothappprofiles/avrcp/remconbeareravrcp/src/passthroughhelper.cpp	Mon Jan 18 20:28:57 2010 +0200
@@ -0,0 +1,443 @@
+// Copyright (c) 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:
+//
+
+#include "avrcplog.h"
+#include "avrcprouter.h"
+#include "avrcputils.h"
+#include "controlcommand.h"
+#include "passthroughhelper.h"
+#include "controlbearer.h"
+
+
+CPassthroughHelper* CPassthroughHelper::NewL(CRcpRouter& aRouter, CRemConBearerAvrcp& aBearer, CDeltaTimer& aTimer)
+	{
+	CPassthroughHelper* helper = new(ELeave)CPassthroughHelper(aRouter, aBearer, aTimer);
+	CleanupStack::PushL(helper);
+	helper->ConstructL();
+	CleanupStack::Pop(helper);
+	return helper;
+	}
+
+CPassthroughHelper::~CPassthroughHelper()
+	{
+	}
+	
+CPassthroughHelper::CPassthroughHelper(CRcpRouter& aRouter, CRemConBearerAvrcp& aBearer, CDeltaTimer& aTimer)
+	: iRouter(aRouter)
+	, iBearer(aBearer)
+	, iTimer(aTimer)
+	{
+	}
+
+void CPassthroughHelper::ConstructL()
+	{
+	}
+
+void CPassthroughHelper::Disconnect()
+	{
+	LOG_FUNC
+	
+	// We handle the last command as a special case to ensure the presses and
+	// releases are balanced from a clients' point of view.
+	if(iPreviousPassthrough)
+		{
+		iRouter.RemoveFromSendQueue(*iPreviousPassthrough);
+		BalanceHandledCommand(*iPreviousPassthrough);
+		}
+	}
+
+/** Sends a response to the remote device.
+
+Because of the 100ms timeout for responses we send a response to
+passthrough commands as soon as we receive them.  This means the real
+response from RemCon is currently ignored.  A real response is
+only sent if this is a vendor dependent command.
+
+@param aCommand The command to respond to.
+@param aErr The result of handling the command.  KErrNone if successful.
+			KErrNotSupported if this operation is not supported.
+*/		
+void CPassthroughHelper::Respond(CControlCommand& aCommand, TInt aErr)
+	{
+	LOG_FUNC
+	aCommand.SetResponseType(aErr);
+	iRouter.AddToSendQueue(aCommand);
+	}
+
+/** To be called on completion of command handling.
+
+This aggregates the handler's tidying up of a finished
+command.
+
+@param aCommand The command to tidy up.
+*/	
+void CPassthroughHelper::HandledCommand(CControlCommand& aCommand)
+	{
+	LOG_FUNC
+
+	// If this was the previous passthrough reset it to NULL
+	if(iPreviousPassthrough == &aCommand)
+		{
+		iPreviousPassthrough = NULL;
+		}
+	
+	aCommand.CancelTimer(iTimer);
+	aCommand.DecrementUsers();
+	}
+
+/** Handles incoming passthrough command.
+
+How the command is handled is dependent on this and the previous command.
+
+Previous command	This command 	Behaviour
+Press_op1			Press_op1		discard
+Press_op1			Release_op1		press_op1 hold timer not expired - generate click_op1
+									press_op1 release timer not expired - generate release_op1
+Press_op1			Press_op2		generate release_op1, start hold timer for press_op2
+Press_op1			Release_op2		press_op1 hold timer not expired - generate click_op1, discard release_op2
+									press_op1 release timer not expired - generate release_op1, discard release_op2
+Release_op1			Press_op1		start hold timer for press_op1
+Release_op1			Release_op1		discard
+Release_op1			Press_op2		start hold timer for press_op2	
+Release_op1			Release_op2		discard
+
+@param aCommand The command to handle.
+@leave System wide error code.
+*/
+void CPassthroughHelper::HandlePassthrough(CControlCommand& aCommand)
+	{
+	LOG_FUNC
+	
+	// We can't map Vendor Unique passthrough command to clicks as we do
+	// not know how the button action is conveyed to the API.  All we can
+	// do is try and ensure that we try to generate a release if one is 
+	// missing by asking the convert.  We also need to consider them as
+	// part of the passthrough history for how other passthrough commands
+	// should be interpreted.
+	TUint8 thisOpId;
+	aCommand.Frame().OperationId(thisOpId);
+	if(!aCommand.IsAvrcpPassthrough())
+		{
+		aCommand.SetClick(EFalse);
+		}
+	
+	// Behaviour depends on previous command
+	TUint8 prevOpId;
+	if(iPreviousPassthrough)
+		{
+		__ASSERT_ALWAYS(iPreviousPassthrough->ButtonAct() == AVCPanel::EButtonPress,
+	 		AvrcpUtils::Panic(EAvrcpPressNotPreviousPassthroughCommand));
+
+		iPreviousPassthrough->Frame().OperationId(prevOpId);
+		}
+		
+	if(aCommand.ButtonAct() == AVCPanel::EButtonPress)
+		{
+		// Respond now so we can decrement users of aCommand at will
+		Respond(aCommand, KErrNone);		
+			
+		if(iPreviousPassthrough)
+			{
+			// previous is press, current is press
+			if(prevOpId == thisOpId)
+				{
+				// prevCommand is for same opId as this one.  Drop this
+				// command and reset release timer for prevCommand.
+				aCommand.DecrementUsers();
+				
+				iPreviousPassthrough->CancelTimer(iTimer);
+				// If still flagged as a click then the user won't have been notified
+				// of the press yet - so do it now.
+				if(iPreviousPassthrough->Click())
+					{
+					iPreviousPassthrough->SetClick(EFalse);
+					iPreviousPassthrough->SetCoreButtonAction(ERemConCoreApiButtonPress, ETrue);
+					iBearer.MrcciNewCommand(*iPreviousPassthrough, iPreviousPassthrough->ClientId());
+					}
+				StartReleaseTimer(*iPreviousPassthrough);
+				}
+			else if(iPreviousPassthrough->Click())
+				{
+				// prevCommand is click, Hold timer not expired.  Generate click
+				// to RemCon then start waiting for click.
+				iPreviousPassthrough->CancelTimer(iTimer);
+				iPreviousPassthrough->SetCoreButtonAction(ERemConCoreApiButtonClick, ETrue);
+				iBearer.MrcciNewCommand(*iPreviousPassthrough, iPreviousPassthrough->ClientId());
+				
+				HandledCommand(*iPreviousPassthrough);
+									
+				// Start waiting for click (core api) or release (vendor unique)
+				NewPress(aCommand);
+				}
+			else
+				{
+				// prevCommand is not click, Release timer not expired.  Generate
+				// release to RemCon then start waiting for click.
+				BalanceHandledCommand(*iPreviousPassthrough);
+						
+				// Start waiting for click (core api) or release (vendor unique)
+				NewPress(aCommand);
+				}
+			}
+		else
+			{
+			// No previous command, current is press
+			
+			// Start waiting for click (core api) or release (vendor unique)
+			NewPress(aCommand);
+			}	
+		}
+	else if(iPreviousPassthrough)
+		{
+		// previous is press, current is release
+		if(prevOpId == thisOpId)
+			{
+			Respond(aCommand, KErrNone);
+					
+			if(iPreviousPassthrough->Click())
+				{
+				// Button release for same opId with hold timeout
+				aCommand.DecrementUsers(); // Drop this command.
+				
+				// Cancel hold timer and send the previous command as a click.
+				iPreviousPassthrough->CancelTimer(iTimer); 
+				iPreviousPassthrough->SetCoreButtonAction(ERemConCoreApiButtonClick, ETrue);
+				iBearer.MrcciNewCommand(*iPreviousPassthrough, iPreviousPassthrough->ClientId());
+				
+				HandledCommand(*iPreviousPassthrough);
+				}
+			else
+				{
+				// Button release for same opId, hold timeout expired
+				iPreviousPassthrough->CancelTimer(iTimer);
+				HandledCommand(*iPreviousPassthrough);
+				
+				if(aCommand.IsAvrcpPassthrough())
+					{
+					aCommand.SetCoreButtonAction(ERemConCoreApiButtonRelease, ETrue);
+					}
+				iBearer.MrcciNewCommand(aCommand, aCommand.ClientId());
+				aCommand.DecrementUsers();
+				}
+			}
+		else
+			{
+			// Drop this release and continue waiting for release to
+			// prevCommand.
+			Respond(aCommand, KErrNone);
+			aCommand.DecrementUsers();
+			}
+		}
+	else
+		{
+		// No prevCommand, this is a release
+		// To get here either:
+		// - this is a spurious release
+		// - we've given up waiting for this
+		
+		// Because responses must be made within 100ms we respond before waiting
+		// to see what RemCon thinks.
+		Respond(aCommand, KErrNone);
+		aCommand.DecrementUsers();
+		}
+	}
+
+void CPassthroughHelper::NewPress(CControlCommand& aCommand)
+	{
+	LOG_FUNC
+	__ASSERT_DEBUG(!iPreviousPassthrough, AVRCP_PANIC(EPreviousPassthroughNonNullReplacing));
+	
+	iPreviousPassthrough = &aCommand;
+	
+	if(aCommand.IsAvrcpPassthrough())
+		{
+		StartHoldTimer(aCommand);
+		}
+	else
+		{
+		iBearer.MrcciNewCommand(aCommand, aCommand.ClientId());
+		StartReleaseTimer(aCommand);
+		}
+	}
+
+/** To be called on completion of command handling for an unbalanced press.
+
+This is a special version of the HandledCommand which which assumes the
+command has been notified to the user and so balances the notification
+of a press with a generated release command.
+
+@param aCommand The command to tidy up.
+*/
+void CPassthroughHelper::BalanceHandledCommand(CControlCommand& aCommand)
+	{
+	LOG_FUNC
+	// CancelTimer also frees the timer entries, so needs to be called
+	// here even though there's no pending entry.
+	aCommand.CancelTimer(iTimer);
+	
+	// If this was the previous passthrough reset it to NULL
+	if(iPreviousPassthrough == &aCommand)
+		{
+		iPreviousPassthrough = NULL;
+		}
+	
+	// Here we handle the case that a press has been informed but a release has yet
+	// to be reported, and if it hasn't we provide the notification.
+	if(aCommand.ButtonAct() == AVCPanel::EButtonPress && !aCommand.Click())
+		{
+		if(aCommand.IsAvrcpPassthrough())
+			{
+			// FIXME I think we might need a new transaction id here?
+			TRAPD(err, aCommand.ReSetCoreButtonActionL(ERemConCoreApiButtonRelease, ETrue));
+			if (err == KErrNone)
+				{
+				iBearer.MrcciNewCommand(aCommand, aCommand.ClientId());
+				}
+				else
+				{
+				// just silently drop this command, do not send command to remconserv
+				}
+			}
+		else
+			{
+			// Need see if we can get a valid release from the converter.  Pass
+			// it the same AVC Frame as the press, but with the button action 
+			// flipped to release.  If we can't do this then there's not a
+			// lot we can do.  
+			// FIXME
+			}
+		}
+	aCommand.iHandlingLink.Deque();
+	aCommand.DecrementUsers();
+	}
+
+
+//------------------------------------------------------------------------------------
+// Timer functions
+//------------------------------------------------------------------------------------
+
+/** Starts the hold timer (only applicable to press commands).
+
+This is the timer to determine whether a command will be passed to RemCon
+as a click.  If the timer expires before a matching release is received
+the press and release will be sent separately.  Otherwise,  a click is 
+sent.
+
+@param aCommand The command to start the timer for.
+*/	
+void CPassthroughHelper::StartHoldTimer(CControlCommand& aCommand)
+	{
+	LOG_FUNC
+	
+	// These cannot fail as we use placement new	
+	TAvrcpTimerExpiryInfo* timerInfo = new(aCommand.TimerExpiryInfo())TAvrcpTimerExpiryInfo(this, aCommand);
+	
+	TCallBack callback(HoldExpiry, timerInfo);
+	TDeltaTimerEntry* timerEntry = new(aCommand.TimerEntry())TDeltaTimerEntry(callback);
+	
+	iTimer.Queue(KRcpHoldThreshold, *timerEntry);
+	}
+
+/** Callback when hold timer expires.
+
+This is a static forwarding function.
+
+@param aExpiryInfo The information used by the real HoldExpiry to
+					deal with the timer expiry.
+*/	
+TInt CPassthroughHelper::HoldExpiry(TAny* aExpiryInfo)
+	{
+	LOG_STATIC_FUNC
+	TAvrcpTimerExpiryInfo *timerInfo = reinterpret_cast<TAvrcpTimerExpiryInfo*>(aExpiryInfo);
+	(reinterpret_cast<CPassthroughHelper*>(timerInfo->iHandler))->HoldExpiry(timerInfo->iCommand);
+	
+	return KErrNone;
+	}
+	
+/** Deals with expiry of hold timer.
+
+1) This is not a click.  Set click to false for this command.
+2) Inform RemCon of available press command
+3) Start release timer.
+
+@param aCommand	The CControlCommand that has expired.
+*/
+void CPassthroughHelper::HoldExpiry(CControlCommand& aCommand)
+	{
+	LOG_FUNC
+	__ASSERT_DEBUG((aCommand.ButtonAct() == AVCPanel::EButtonPress), AvrcpUtils::Panic(EAvrcpHoldExpiryForRelease));
+	
+	// We haven't received a release soon enough to treat this as
+	// a click.  Send the press on to RemCon and wait for a release.
+	// CancelTimer also frees the timer entries, so needs to be called
+	// here even though there's no pending entry.
+	aCommand.CancelTimer(iTimer);
+	aCommand.SetClick(EFalse);
+	aCommand.SetCoreButtonAction(ERemConCoreApiButtonPress, ETrue);
+	iBearer.MrcciNewCommand(aCommand, aCommand.ClientId());
+	StartReleaseTimer(aCommand);
+	}
+
+/** Starts the release timer (only applicable to press commands).
+
+If a release is not received quickly enough to treat this press as
+a click, the release timer is started.  2s after receiving a button
+press we should assume the release if we haven't received one.
+
+@param aCommand The command to start the timer for.
+*/		
+void CPassthroughHelper::StartReleaseTimer(CControlCommand& aCommand)
+	{
+	TAvrcpTimerExpiryInfo* timerInfo = new(aCommand.TimerExpiryInfo())TAvrcpTimerExpiryInfo(this, aCommand);
+
+	TCallBack callback(ReleaseExpiry, timerInfo);
+	TDeltaTimerEntry* timerEntry = new(aCommand.TimerEntry())TDeltaTimerEntry(callback);
+	
+	iTimer.Queue(KRcpIncomingButtonReleaseTimeout, *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 CPassthroughHelper::ReleaseExpiry(TAny* aExpiryInfo)
+	{
+	LOG_STATIC_FUNC
+	TAvrcpTimerExpiryInfo *timerInfo = reinterpret_cast<TAvrcpTimerExpiryInfo*>(aExpiryInfo);
+	(reinterpret_cast<CPassthroughHelper*>(timerInfo->iHandler))->ReleaseExpiry(timerInfo->iCommand);
+	
+	return KErrNone;
+	}
+
+/** Deals with expiry of release timer.
+
+1) Generate release for this command.
+2) Inform RemCon of available release command.
+
+@param aCommand	The CControlCommand that has expired.
+*/
+void CPassthroughHelper::ReleaseExpiry(CControlCommand& aCommand)
+	{
+	LOG_FUNC
+	__ASSERT_DEBUG((aCommand.ButtonAct() == AVCPanel::EButtonPress), AvrcpUtils::Panic(EAvrcpReleaseExpiryForRelease));
+	__ASSERT_DEBUG(!aCommand.Click(), AvrcpUtils::Panic(EAvrcpReleaseExpiryForClick));
+	
+	// We haven't received a release within the allotted time.  Assume
+	// one and generate it to RemCon.
+	BalanceHandledCommand(aCommand);
+	}