/*
* Copyright (c) 2010 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: Pairing handler for local device initiated pairing
*
*/
#include "btnotifoutgoingpairinghandler.h"
#include <btengconstants.h>
#include <btservices/btdevextension.h>
#include "btnotifsecuritymanager.h"
#include "bluetoothtrace.h"
#include "bluetoothnotification.h"
#include "btnotifserver.h"
#include "btnotifconnectiontracker.h"
#include "btnotificationmanager.h"
/** Length of the default PIN. */
const TInt KDefaultHeadsetPinLength = 4;
/** Maximum repeated outgoing pairing attempt.
* if the pairing fails the UI specs says
* we can ask twice the user if he/she want
* to retry pairing.
*/
const TInt KMaxRepeatedPairingAttempt = 2;
enum TPairingStageId
{
/**
* no pairing operation ongoing
*/
ENoBonding = 0,
/**
* pair with dedicated bonding method
*/
EDedicatedBonding = 200,
/**
* pair with general bonding by establishing L2CAP connection.
*/
EGeneralBonding,
/**
* delaying next pairing request for a while
*/
EGeneralBondingRetryTimer,
/**
* The last pairing retry
*/
EGeneralBondingRetry,
/**
* disconnecting physical link after pairing operation.
*
* todo: not used yet.
*/
EDisconnectLinkAfterBonding,
};
/** SDP PSM (used for pairing) */
const TInt KSDPPSM = 0x0001;
// Delay time to void Repeated Attempts on pairing
const TInt KGeneralBondingRetryDelayMicroSeconds = 5000000; // 5.0s
// ======== MEMBER FUNCTIONS ========
// ---------------------------------------------------------------------------
// C++ default constructor
// ---------------------------------------------------------------------------
//
CBTNotifOutgoingPairingHandler::CBTNotifOutgoingPairingHandler( CBTNotifSecurityManager& aParent, const TBTDevAddr& aAddr)
: CBTNotifBasePairingHandler( aParent, aAddr )
{
iDialogIsOrphan = EFalse;
}
// ---------------------------------------------------------------------------
// Symbian 2nd-phase constructor
// ---------------------------------------------------------------------------
//
void CBTNotifOutgoingPairingHandler::ConstructL()
{
BaseConstructL();
User::LeaveIfError( iTimer.CreateLocal() );
iPairingAttempt = KMaxRepeatedPairingAttempt;
}
// ---------------------------------------------------------------------------
// NewL
// ---------------------------------------------------------------------------
//
CBTNotifBasePairingHandler* CBTNotifOutgoingPairingHandler::NewL( CBTNotifSecurityManager& aParent,
const TBTDevAddr& aAddr )
{
CBTNotifOutgoingPairingHandler* self = new( ELeave ) CBTNotifOutgoingPairingHandler( aParent, aAddr );
CleanupStack::PushL( self );
self->ConstructL();
CleanupStack::Pop( self );
return self;
}
// ---------------------------------------------------------------------------
// Destructor
// ---------------------------------------------------------------------------
//
CBTNotifOutgoingPairingHandler::~CBTNotifOutgoingPairingHandler()
{
if ( iActive )
{
iActive->Cancel();
}
iBondingSession.Close();
iSocket.Close();
iTimer.Close();
if( iNotification && !iDialogIsOrphan)
{
// Clear the notification callback, we cannot receive them anymore.
iNotification->RemoveObserver();
iNotification->Close(); // Also dequeues the notification from the queue.
iNotification = NULL;
}
if( iNotification && iDialogIsOrphan)
{
// Clear the notification callback, we cannot receive them anymore.
iNotification->RemoveObserver();
}
}
// ---------------------------------------------------------------------------
// Simply deny the request as this is handing outgoing pairing
// ---------------------------------------------------------------------------
//
TInt CBTNotifOutgoingPairingHandler::ObserveIncomingPair( const TBTDevAddr& aAddr )
{
if(iAddr == aAddr)
{
return KErrNone;
}
else
{
return KErrServerBusy;
}
}
// ---------------------------------------------------------------------------
// Accept the request only this device is not busy with another pairing request.
// ---------------------------------------------------------------------------
//
void CBTNotifOutgoingPairingHandler::HandleOutgoingPairL( const TBTDevAddr& aAddr, TUint aCod )
{
BOstrace1(TRACE_DEBUG,DUMMY_DEVLIST,_L(" cod 0x%08x"), aCod );
if ( iActive->IsActive() || aAddr != iAddr )
{
// we don't allow another pairing request.
User::Leave( KErrServerBusy );
}
iAddr = aAddr;
iCod = TBTDeviceClass( aCod );
UnSetPairResult();
iParent.UnpairDevice( iAddr );
if ( CBtDevExtension::IsHeadset( iCod ) )
{
// If the devie is a headset, set to 0000 pin auto pairing
iPairMode = EBTOutgoingHeadsetAutoPairing;
}
else
{
iPairMode = EBTOutgoingNoneHeadsetPairing;
}
DoPairingL();
}
// ---------------------------------------------------------------------------
// Cancels an outstanding pair request by self-destruct
// ---------------------------------------------------------------------------
//
void CBTNotifOutgoingPairingHandler::CancelOutgoingPair()
{
BOstraceFunctionEntry0( DUMMY_DEVLIST );
ShowPairingFailureDialog();
iParent.RenewPairingHandler( NULL );
}
// ---------------------------------------------------------------------------
// when phone initiated a pairing request towards a headset,
// Pin code 0000 is first tried.
// ---------------------------------------------------------------------------
//
void CBTNotifOutgoingPairingHandler::GetPinCode(
TBTPinCode& aPin, const TBTDevAddr& aAddr, TInt aMinPinLength )
{
BOstraceFunctionEntry0( DUMMY_DEVLIST );
aPin().iLength = 0;
if ( aMinPinLength <= KDefaultHeadsetPinLength
&& aAddr == iAddr
&& iPairMode == EBTOutgoingHeadsetAutoPairing)
{
// if the pairing requires a stronger security level (indicated
// by aMinPinLength),
// 0000 will not be supplied as it does not meet the security
// requirements
const TUint8 KZeroPinValue = '0';
for (TInt i = 0; i < KDefaultHeadsetPinLength; ++i)
{
aPin().iPIN[i] = KZeroPinValue;
}
aPin().iLength = KDefaultHeadsetPinLength;
}
}
// ---------------------------------------------------------------------------
// Abort pairing handling, request the owner to destroy this.
// ---------------------------------------------------------------------------
//
void CBTNotifOutgoingPairingHandler::StopPairHandling( const TBTDevAddr& aAddr )
{
BOstraceFunctionEntry0( DUMMY_DEVLIST );
if ( aAddr == iAddr )
{
iParent.OutgoingPairCompleted( KErrCancel );
iParent.RenewPairingHandler( NULL );
}
}
// ---------------------------------------------------------------------------
// Pairing result will be received when pairing operation completes.
// ---------------------------------------------------------------------------
//
void CBTNotifOutgoingPairingHandler::DoHandlePairServerResult( TInt aResult )
{
BOstraceFunctionEntry0( DUMMY_DEVLIST );
if (aResult == (KHCIErrorBase-EPairingNotAllowed))
{
// if EPairingNotAllowed is recieved then any further pairing attempts will fail
// so don't attampt to pair
iPairMode = EBTOutgoingPairNone;
ShowPairingFailureDialog();
iParent.OutgoingPairCompleted( aResult );
iParent.RenewPairingHandler( NULL );
}
}
// ---------------------------------------------------------------------------
// Cancels possible outstanding pairing and notify user pair success.
// ---------------------------------------------------------------------------
//
void CBTNotifOutgoingPairingHandler::DoHandleRegistryNewPairedEvent(
const TBTNamelessDevice& aDev )
{
BOstraceFunctionEntry0( DUMMY_DEVLIST );
TInt err( KErrNone );
// If pairing was performed using Just Works mode, we set a
// UICookie to indicate that the device is successfully
// bonded so that this device will be listed in paired device view of
// bluetooth application:
if ( aDev.LinkKeyType() == ELinkKeyUnauthenticatedNonUpgradable )
{
BOstrace0(TRACE_DEBUG,DUMMY_DEVLIST,_L("[BTNOTIF] Outgoing Pairing, Just Works pairing"));
err = iParent.AddUiCookieJustWorksPaired( aDev );
}
iActive->Cancel();
SetPairResult( err ? err : KErrNone );
if(err == KErrNone)
{
// Trust the device
if(iTrustDevice)
{
iParent.TrustDevice(iAddr);
}
TRAP_IGNORE(ShowPairingResultNoteL(err));
}
iParent.OutgoingPairCompleted( err );
iParent.RenewPairingHandler( NULL );
}
// ---------------------------------------------------------------------------
// From class MBTNotifPairingAOObserver.
// Based on the result code, decides the next operation, either try pairing
// with another mode, or complete pair request.
// ---------------------------------------------------------------------------
//
void CBTNotifOutgoingPairingHandler::RequestCompletedL(
CBtSimpleActive* aActive, TInt aStatus )
{
BOstraceFunctionEntry0( DUMMY_DEVLIST );
BOstraceExt3(TRACE_DEBUG,DUMMY_DEVLIST,_L("reqid %d, status: %d, pair mode %d "), aActive->RequestId(),
aStatus, iPairMode);
if( aActive->RequestId() == EDedicatedBonding &&
( aStatus == KErrRemoteDeviceIndicatedNoBonding ||
( aStatus && iPairMode != EBTOutgoingNoneHeadsetPairing && iPairMode != EBTOutgoingPairNone ) ) )
{
// try general pairing if the remote doesn't have dedicated bonding, or
// pairing fails with a headset.
DoPairingL();
}
else if ( aStatus && iPairMode == EBTOutgoingHeadsetAutoPairing )
{
iPairMode = EBTOutgoingHeadsetManualPairing;
// auto pairing with headset failed, try to pair again with manual pin:
BOstrace0(TRACE_DEBUG,DUMMY_DEVLIST,_L(" auto pairing failed, switch to manual pairing"));
DoPairingL();
}
else if ( aStatus && aActive->RequestId() == EGeneralBonding &&
iPairMode == EBTOutgoingHeadsetManualPairing )
{
// pairing headset with manual pin failed, wait for a while and try again:
iActive->SetRequestId( EGeneralBondingRetryTimer );
iTimer.After( iActive->iStatus, KGeneralBondingRetryDelayMicroSeconds );
iActive->GoActive();
}
else if( aActive->RequestId() == EGeneralBondingRetryTimer )
{
// try to pair headset again with manual pin again:
DoPairingL();
}
else if ( aStatus )
{
// we only starts showing note if pairing failed.
// For a successful pair, we must wait until registry has been updated.
if ( !IsPairResultSet() )
{
SetPairResult( aStatus );
}
if ( aStatus <= KHCIErrorBase - EUnknownOpcode)
{
// retry pairing
if(iPairingAttempt > 0)
{
if(aActive->RequestId() == EGeneralBondingRetry && iPairMode == EBTOutgoingHeadsetManualPairing)
{
// Headset pairing failed, reset and try again from auto pairing
iActive->SetRequestId(EGeneralBonding);
}
iPairingAttempt --;
ShowPairingRetryDialog();
}
else
{
iPairingAttempt --;
iParent.OutgoingPairCompleted( aStatus );
ShowPairingFailureDialog();
iParent.RenewPairingHandler( NULL );
}
}
else
{
iParent.OutgoingPairCompleted( aStatus );
ShowPairingFailureDialog();
iParent.RenewPairingHandler( NULL );
}
}
}
// ---------------------------------------------------------------------------
// From class MBTEngActiveObserver.
// cancels an outstanding request according to the given id.
// ---------------------------------------------------------------------------
//
void CBTNotifOutgoingPairingHandler::CancelRequest( TInt aRequestId )
{
BOstraceFunctionEntry0( DUMMY_DEVLIST );
switch ( aRequestId )
{
case EDedicatedBonding:
{
iBondingSession.Close();
break;
}
case EGeneralBonding:
case EGeneralBondingRetry:
{
iSocket.CancelConnect();
iSocket.Close();
break;
}
case EGeneralBondingRetryTimer:
{
iTimer.Cancel();
break;
}
}
}
// ---------------------------------------------------------------------------
// From class MBTEngActiveObserver.
// Handles a leave in RequestCompleted by self-destructing.
// ---------------------------------------------------------------------------
//
void CBTNotifOutgoingPairingHandler::HandleError(
CBtSimpleActive* aActive, TInt aError )
{
BOstrace1(TRACE_DEBUG,DUMMY_DEVLIST,_L("error: %d"), aError );
// Our RunL can actually not leave, so we should never reach here.
(void) aActive;
iParent.OutgoingPairCompleted( aError );
iParent.RenewPairingHandler( NULL );
}
// ---------------------------------------------------------------------------
// decide the next state and issue pair request
// ---------------------------------------------------------------------------
//
void CBTNotifOutgoingPairingHandler::DoPairingL()
{
BOstraceFunctionEntry0( DUMMY_DEVLIST );
TPairingStageId currentMode = ( TPairingStageId ) iActive->RequestId();
ASSERT( !iActive->IsActive() );
TPairingStageId nextMode( EGeneralBonding );
// if running BTv2.0 stack, dedicated bonding method
// is not available.
if ( currentMode == ENoBonding && iParent.PairingServer() != NULL )
{
nextMode = EDedicatedBonding;
}
else if(currentMode == EGeneralBondingRetryTimer)
{
nextMode = EGeneralBondingRetry;
}
BOstraceExt2(TRACE_DEBUG,DUMMY_DEVLIST,_L("[BTNOTIF] CBTEngOtgPair::DoPairingL: bonding mode: pre %d, next %d"), currentMode, nextMode);
iActive->SetRequestId( nextMode );
if ( nextMode == EDedicatedBonding )
{
iBondingSession.Start( *iParent.PairingServer(), iAddr, iActive->RequestStatus() );
}
else
{
TBTServiceSecurity sec;
sec.SetAuthentication( ETrue );
iSockAddr.SetBTAddr( iAddr );
iSockAddr.SetPort(KSDPPSM);
iSockAddr.SetSecurity( sec );
iSocket.Close();
User::LeaveIfError( iSocket.Open( iParent.SocketServ(), KL2CAPDesC ) );
iSocket.Connect( iSockAddr, iActive->RequestStatus() );
}
iActive->GoActive();
BOstraceFunctionExit0( DUMMY_DEVLIST );
}
// ---------------------------------------------------------------------------
// From class MBTNotificationResult.
// Handle a result from a user query.
// ---------------------------------------------------------------------------
//
void CBTNotifOutgoingPairingHandler::MBRDataReceived( CHbSymbianVariantMap& aData )
{
BOstraceFunctionEntry0( DUMMY_DEVLIST );
(void) aData;
BOstraceFunctionExit0( DUMMY_DEVLIST );
}
// ---------------------------------------------------------------------------
// From class MBTNotificationResult.
// The notification is finished.
// ---------------------------------------------------------------------------
//
void CBTNotifOutgoingPairingHandler::MBRNotificationClosed( TInt aError, const TDesC8& aData )
{
BOstraceFunctionEntryExt( DUMMY_DEVLIST, this, aError );
// First unregister from the notification, so we can already get the next one.
iNotification->RemoveObserver();
iNotification = NULL;
TRAP_IGNORE( NotificationClosedL( aError, aData ) );
BOstraceFunctionExit1( DUMMY_DEVLIST, this );
}
// ---------------------------------------------------------------------------
// Get and configure a notification.
// ---------------------------------------------------------------------------
//
void CBTNotifOutgoingPairingHandler::PrepareNotificationL( TBluetoothDialogParams::TBTDialogType aType,
TBTDialogResourceId aResourceId )
{
BOstraceFunctionEntry0( DUMMY_DEVLIST );
iNotification =
iParent.ConnectionTracker().NotificationManager()->GetNotification();
User::LeaveIfNull( iNotification ); // For OOM exception, leaves with KErrNoMemory
iNotification->SetObserver( this );
iNotification->SetNotificationType( aType, aResourceId );
const CBtDevExtension* dev = iParent.BTDevRepository().Device(iAddr);
if(dev)
{
User::LeaveIfError(iNotification->SetData( TBluetoothDeviceDialog::EDeviceName, dev->Alias()));
}
else
{
TBTDeviceName name;
iAddr.GetReadable(name);
User::LeaveIfError(iNotification->SetData( TBluetoothDeviceDialog::EDeviceName, name));
NOTIF_NOTHANDLED( !err )
}
iParent.ConnectionTracker().NotificationManager()->QueueNotificationL( iNotification);
NOTIF_NOTHANDLED( !err )
BOstraceFunctionExit0( DUMMY_DEVLIST );
}
// ---------------------------------------------------------------------------
// The notification is finished, handle the result.
// ---------------------------------------------------------------------------
//
void CBTNotifOutgoingPairingHandler::NotificationClosedL( TInt aError, const TDesC8& aData )
{
BOstraceFunctionEntryExt( DUMMY_DEVLIST, this, aError );
(void)aError;
// Read the result.
TPckgC<TBool> result( EFalse );
result.Set( aData.Ptr(), result.Length() ); // Read the part containing the result
// Set a pointer descriptor to capture the remaining data, if any.
TPtrC8 dataPtr( aData.Mid( result.Length() ) );
if(result() && iPairingAttempt >= 0)
{
HandleOutgoingPairL(iAddr,iCod.DeviceClass());
}
else
{
iPairingAttempt = KMaxRepeatedPairingAttempt; // reset the counter
iParent.OutgoingPairCompleted( KErrCancel );
iParent.RenewPairingHandler( NULL );
}
BOstraceFunctionExit1( DUMMY_DEVLIST, this );
}
// ---------------------------------------------------------------------------
// Show a dialog to ask the user to retry the pairing
// ---------------------------------------------------------------------------
//
void CBTNotifOutgoingPairingHandler::ShowPairingRetryDialog()
{
PrepareNotificationL( TBluetoothDialogParams::EQuery, EPairingFailureRetry);
}
// ---------------------------------------------------------------------------
// Show a dialog to tell the user pairing retry attempt failed
// ---------------------------------------------------------------------------
//
void CBTNotifOutgoingPairingHandler::ShowPairingFailureDialog()
{
if(!iPairingCancelledByUser)
{
iDialogIsOrphan = ETrue;
PrepareNotificationL( TBluetoothDialogParams::EQuery, EPairingFailureOk );
}
}