/*
* Copyright (c) 2009, 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:  CAknPhysics
*
*/


#include <aknphysics.h>
#include <aknphysicsobserveriface.h>
#include <e32debug.h>

#include "aknphysicsconstants.h"
#include "aknphysicsengine.h"
#include "aknphysicsparameterprovider.h"
#include "aknphysicsrestrictor.h"
#include "aknphysicsconeobserver.h"
#include <touchfeedback.h>

const TInt KSamePositionStopCount( 3 );

_LIT( KNullThreadProcessName, "ekern.exe*" );
_LIT( KNullThreadName, "::Null" );

#undef AKNHIGHRESPERIODIC_PROFILE

/**
 * Opens a handle to the null thread.
 */
static TBool FindNullThread( RThread& aThread )
    {
    TFindProcess fp( KNullThreadProcessName );
    TFullName kernelName;
    if ( fp.Next( kernelName ) == KErrNone )
        {
        kernelName.Append( KNullThreadName );
        
        TFindThread ft( kernelName );
        TFullName threadName;
        if ( ft.Next( threadName ) == KErrNone )
            {
            if ( aThread.Open( threadName ) != KErrNone )
                {
                return EFalse;
                }
            }
        }        

    return ( aThread.Handle() != 0 );
    }


/**
 * Periodic timer using CTimer::HighRes.
 */
NONSHARABLE_CLASS( CAknHighResPeriodic ) : public CTimer
    {
public:

    /**
     * Creates new timer instance.
     * 
     * @param  aPriority    Priority of the timer.
     * @param  aNullThread  Open thread handle to the null thread,
     *                      ownership is not transferred.
     */
    static CAknHighResPeriodic* NewL( TInt aPriority,
                                      RThread* aNullThread );

    /**
     * Destructor.
     */
    ~CAknHighResPeriodic();

    /**
     * Starts periodic timer.
     * 
     * @param  aDelay      Initial delay.
     * @param  anInterval  Minimum interval between callback.
     * @param  aCallBack   Callback to invoke.
     */
    void Start( TTimeIntervalMicroSeconds32 aDelay,
                TTimeIntervalMicroSeconds32 anInterval,
                TCallBack aCallBack );

    /**
     * Sets minimum time between end of callback to beginning of next one.
     *
     * This is one approach to try to make sure that there is some time for
     * other processes. If this process can handle callback quickly,
     * this won't slow down frame rate.
     * 
     * @param  aDelay  Minimum time between callbacks.
     */
    void SetMinCallBackPeriod( TTimeIntervalMicroSeconds32 aDelay );

protected:

    /**
     * C++ constructor.
     * 
     * @param  aPriority    Priority of the timer.
     * @param  aNullThread  Open thread handle to the null thread,
     *                      ownership is not transferred.
     */
    CAknHighResPeriodic( TInt aPriority, RThread* aNullThread );
    
    /**
     * Called upon timer completion.
     */
    void RunL();

private:

    /**
     * Prints out the CPU time given to the null thread between this and
     * the previous call to this method.
     */
    void DumpNull();
    
private:

    /** Timer interval. */
    TTimeIntervalMicroSeconds32 iInterval;
    
    /** Callback of the timer. */
    TCallBack iCallBack;
    
    /** Is the end time valid, i.e. has the timer been completed at least once. */
    TBool iEndTimeValid;
    
    /** Time after the execution of the last timer callback. */
    TTime iEndTime;
    
    /** Minimum interval between callbacks. */
    TInt64 iMinCallBackPeriod;
    
    TBool iWaiting;
    
    /** Pointer to the null thread handle, not own. */
    RThread* iDebugNullThread;
    
    /** Null thread CPU time after the last callback. */ 
    TInt64 iDebugPreviousTime;
    };


// ---------------------------------------------------------------------------
// Creates new timer instance
// ---------------------------------------------------------------------------
//
CAknHighResPeriodic* CAknHighResPeriodic::NewL( TInt aPriority,
                                                RThread* aNullThread )
    {
    CAknHighResPeriodic* self =
        new (ELeave) CAknHighResPeriodic( aPriority, aNullThread );
    CleanupStack::PushL( self );
    self->ConstructL();
    CleanupStack::Pop( self );
    return self;
    }


// ---------------------------------------------------------------------------
// Destructor
// ---------------------------------------------------------------------------
//
CAknHighResPeriodic::~CAknHighResPeriodic()
    {
    }


// ---------------------------------------------------------------------------
// Starts the periodic timer.
// ---------------------------------------------------------------------------
//
void CAknHighResPeriodic::Start( TTimeIntervalMicroSeconds32 aDelay,
                                 TTimeIntervalMicroSeconds32 anInterval,
                                 TCallBack aCallBack)
    {
    iInterval = anInterval.Int();
    iCallBack = aCallBack;
    iEndTimeValid = EFalse;
    iWaiting = EFalse;
    HighRes( aDelay );
    }


// ---------------------------------------------------------------------------
// Sets minimum time between end of callback to beginning of next one.
// ---------------------------------------------------------------------------
//
void CAknHighResPeriodic::SetMinCallBackPeriod(
    TTimeIntervalMicroSeconds32 aDelay )
    {
    if ( aDelay.Int() >= 0 )
        {
        iMinCallBackPeriod = aDelay.Int();
        }
    else
        {
        iMinCallBackPeriod = 0;
        }
    }


// ---------------------------------------------------------------------------
// C++ constructor.
// ---------------------------------------------------------------------------
//
CAknHighResPeriodic::CAknHighResPeriodic( TInt aPriority,
                                          RThread* aNullThread )
    : CTimer( aPriority ), iDebugNullThread( aNullThread )
    {
    CActiveScheduler::Add( this );
    }


// ---------------------------------------------------------------------------
// Prints out the CPU time given to the null thread between this and previous
// call to this method.
// ---------------------------------------------------------------------------
//
void CAknHighResPeriodic::DumpNull()
    {
    TTimeIntervalMicroSeconds time;
    TInt err = iDebugNullThread->GetCpuTime( time );

    if ( !err )
        {
        if ( !iEndTimeValid )
            {
            iDebugPreviousTime = time.Int64();
            }
        else
            {
            TInt64 delta = time.Int64() - iDebugPreviousTime; 
            iDebugPreviousTime = time.Int64();
#ifdef AKNHIGHRESPERIODIC_PROFILE
            _LIT( KDMsg1, "[AKNPHYSICS] Null thread CPU time between frames: %d microseconds" );
            RDebug::Print( KDMsg1, TInt( delta ) );
#endif // AKNHIGHRESPERIODIC_PROFILE
            }
        }
    else
        {
#ifdef AKNHIGHRESPERIODIC_PROFILE
        _LIT( KDMsg2, "[AKNPHYSICS] Error %d in getting null thread CPU time" );
        RDebug::Print( KDMsg2, err );
#endif // AKNHIGHRESPERIODIC_PROFILE
        }
    }


// ---------------------------------------------------------------------------
// Handles timer completion.
// ---------------------------------------------------------------------------
//
void CAknHighResPeriodic::RunL()
    {
    if ( iEndTimeValid && !iWaiting ) 
        {
        TTime now;
        now.UniversalTime();
        
        TInt64 delta = now.MicroSecondsFrom( iEndTime ).Int64();
        if ( delta < 0 )
            {
            delta = 0;
            }

        TInt64 waitTime = iMinCallBackPeriod - delta; 
        
        if ( waitTime >= 0 )
            {
            // Restrict to some sensible min-max period.
            if ( waitTime < KAknHighResMinWait )
                {
                waitTime = KAknHighResMinWait;
                }

            if ( waitTime > KAknHighResMaxWait )
                {
                waitTime = KAknHighResMaxWait;
                }

            TInt timeToWait( waitTime );

#ifdef AKNHIGHRESPERIODIC_PROFILE
            _LIT( KDmsg, "[AKNPHYSICS] - CAknHighResPeriodic::RunL : waiting for %d microseconds" );
            RDebug::Print( KDmsg, timeToWait );
#endif            
            iWaiting = ETrue;
            HighRes( timeToWait );
            return;
            }
        }
    
    HighRes( iInterval );
    iCallBack.CallBack();
    
#ifdef AKNHIGHRESPERIODIC_PROFILE

    if ( iDebugNullThread && iDebugNullThread->Handle() )
        {
        DumpNull();
        }

#endif // AKNHIGHRESPERIODIC_PROFILE
    
    iWaiting = EFalse;
    iEndTime.UniversalTime();
    iEndTimeValid = ETrue;
    }



// ---------------------------------------------------------------------------
// CAknPhysics::NewL()
// ---------------------------------------------------------------------------
//
EXPORT_C CAknPhysics* CAknPhysics::NewL( MAknPhysicsObserver& aObserver,
                                         CCoeControl* aViewControl )
    {
    CAknPhysics* self = new ( ELeave ) CAknPhysics( aObserver );
    CleanupStack::PushL( self );
    self->ConstructL( aViewControl );
    CleanupStack::Pop( self );
    return self;
    }
    
// ---------------------------------------------------------------------------
// CAknPhysics::~CAknPhysics()
// ---------------------------------------------------------------------------
//
CAknPhysics::~CAknPhysics()
    {
    delete iPhysics;
    delete iEngine;
    delete iParamProvider;
    delete iRestrictor;
    delete iConeObserver;

    iNullThread.Close();
    }

// ---------------------------------------------------------------------------
// CAknPhysics::InitPhysicsL()
// ---------------------------------------------------------------------------
//
EXPORT_C void CAknPhysics::InitPhysicsL( const TSize& aWorldSize, 
                                         const TSize& aViewSize, 
                                         TBool aLandscape )
    {
    if ( iRestrictor )
        {
        // Update always the restrictor parameters.
        iRestrictor->UpdatePhysicsEnvironment( aWorldSize,
                                               aViewSize,
                                               aLandscape );
        }
    
    // Do not initialize if all parameters match with old ones
    if ( aWorldSize == iWorldSize && aViewSize == iViewSize 
        && ( ( aLandscape && iLandscape ) || ( !aLandscape && !iLandscape ) ) )
        {
        return;    
        }

    StopPhysics();
    iEngine->DeletePhysics();

    iLandscape = aLandscape;

    iWorldSize = aWorldSize;
    iViewSize = aViewSize;
    if( (iLandscape && aWorldSize.iWidth < aViewSize.iWidth) 
            || (!iLandscape && aWorldSize.iHeight < aViewSize.iHeight) )
        {
        iWorldSize = aViewSize;
        }

    TInt64 ih, x, y, w, h, r;
    if(iLandscape)
        {
        ih = iWorldSize.iWidth;
        x = 1;
        y = 0;
        r = iViewSize.iHeight;
        }
    else
        {
        ih = iWorldSize.iHeight;
        x = 0;
        y = 1;
        r = iViewSize.iWidth;
        }

    w = iViewSize.iWidth;
    h = iViewSize.iHeight;
    
    if ( iEngine )
        {
        iEngine->CreateWorld( iParamProvider->Gravity() );
        iEngine->CreatePlanes( ih, x, y, r );
        iEngine->CreateViewBody( w, h, iParamProvider->ViewMass() );
        }
    }


// ---------------------------------------------------------------------------
// CAknPhysics::StartPhysics
// ---------------------------------------------------------------------------
//
EXPORT_C TBool CAknPhysics::StartPhysics( TPoint& aDrag, 
                                          const TTime& aStartTime )
    {
    // Check that world really exists
    if ( iEngine )
        {
        if ( ( !iLandscape && iWorldSize.iHeight <= iViewSize.iHeight ) ||
                ( iLandscape && iWorldSize.iWidth <= iViewSize.iWidth ) )
            {
            return EFalse;
            }
        }
    else
        {
        return EFalse;
        }
    
    iSuspended = EFalse;

    TTime now;
    now.HomeTime();
    TInt moveTime( now.MicroSecondsFrom( aStartTime ).Int64() );

    TBool startPhysics( EFalse );
    iFlickTimeExceeded = EFalse;

    // Check if drag direction has changed during drag event
    // and receive the changed parameters
    if ( iConeObserver )
        {
        iConeObserver->DragChanged( iLandscape, aDrag, moveTime );
        }

    // Start physics if drag threshold is exceeded
    startPhysics = 
        ( iLandscape && Abs( aDrag.iX ) > DragThreshold() )
        || ( !iLandscape && Abs( aDrag.iY ) > DragThreshold() );
        
    // Check if movetime threshold is exceeded
    if ( startPhysics && moveTime > KFlickMaxDuration )
        {
        iFlickTimeExceeded = ETrue;
        }

    // Start physics always if position reveals empty space
    if ( !startPhysics && iRestrictor )
        {
        TPoint viewPosition( iObserver.ViewPosition() );
        startPhysics = 
            iRestrictor->PositionRevealsEmptySpace( viewPosition );
        }
        
    // Stop FPS logging here so that panning gets logged correctly. Each
    // panning always ends with a flick attempt.
    if ( iParamProvider->FpsLoggingEnabled() )
        {
        iEngine->StopFpsLogging();
        }

    if ( startPhysics && iEngine )
        {
        iEngine->SetViewBodyPosition( iObserver.ViewPosition() );
        StartPhysics();
        ApplyDrag( aDrag, moveTime ); 
        
        if ( iParamProvider->FpsLoggingEnabled() )
            {
            iEngine->StartFpsLogging();
            }
            
        return ETrue;
        }
    return EFalse;
    }


// ---------------------------------------------------------------------------
// CAknPhysics::StopPhysics()
// ---------------------------------------------------------------------------
//
EXPORT_C void CAknPhysics::StopPhysics()
    {
    // Check that world really exists
    if ( !iEngine || !iEngine->WorldExists() )
        {
        return;
        }
    
    iSuspended = EFalse;

    iSamePositionCounter = 0;
    
    if ( iPhysics && iPhysics->IsActive() )
        {
        if ( iParamProvider->FpsLoggingEnabled() )
            {
            iEngine->StopFpsLogging();
            }

        CancelPhysicsTimer();
    
        if ( iEngine )
            {
            iEngine->ResetViewBodyForceAndVelocity();
            }
        iObserver.PhysicEmulationEnded();
        }
    else
        {
        UpdateActionState();
        }
    }


// ---------------------------------------------------------------------------
// CAknPhysics::OngoingPhysicsAction
// ---------------------------------------------------------------------------
//
EXPORT_C TInt CAknPhysics::OngoingPhysicsAction() const
    {
    return iOngoingAction;
    }


// ---------------------------------------------------------------------------
// CAknPhysics::RegisterPanningPosition
// ---------------------------------------------------------------------------
//
EXPORT_C void CAknPhysics::RegisterPanningPosition( const TPoint& aDelta )
    {
    TInt viewSize = !iLandscape ? iViewSize.iHeight : iViewSize.iWidth;
    TInt worldSize = !iLandscape ? iWorldSize.iHeight : iWorldSize.iWidth;
    
    if ( worldSize <= viewSize )
        {
        return;
        }
	
    TTimeIntervalMicroSeconds time;
    TInt err( KErrNone );
    if ( iNullThread.Handle() )
        {
        err = iNullThread.GetCpuTime( time );
        }
    else
        {
        err = KErrBadHandle;
        }
    
    TInt64 nullDelta = !err ? time.Int64() - iNullThreadTime : KMaxTInt64;
    
    TTime now;
    now.UniversalTime();
    
    TInt64 delta = Max( 0, now.MicroSecondsFrom( iTimeOfLastDraw ).Int64() );
    
#ifdef AKNHIGHRESPERIODIC_PROFILE

    _LIT( KDMsg1, "[AKNPHYSICS] - RegisterPanningPosition : Null thread CPU time between frames: %d microseconds" );
    _LIT( KDMsg2, "[AKNPHYSICS] - RegisterPanningPosition : Last draw %d microseconds ago");
    RDebug::Print( KDMsg1, TInt( nullDelta ) );
    RDebug::Print( KDMsg2, TInt( delta ) );
    
    if ( err )
        {
        _LIT( KDMsg3, "[AKNPHYSICS] Error %d in getting null thread CPU time" );
        RDebug::Print( KDMsg3, err );
        }
    
#endif // AKNHIGHRESPERIODIC_PROFILE
    
    TBool drawNow = EFalse;
    if ( nullDelta > FrameDelay() * 1000 ||       // There has been enough idle time.
         delta > MinFrameInterval() * 1000 ||     // Enough time since last draw.
         iTimeOfLastDraw.Int64() == Int64( 0 ) || // First draw.
         nullDelta < 0 )                          // Null thread CPU time not valid.
        {
        drawNow = ETrue;
        }

    iPanningDrawOmitted = !drawNow;
    
    TPoint position( iObserver.ViewPosition() );
    
    TPoint movement( aDelta );

    if ( iRestrictor && iRestrictor->AllowedViewPosition( position ) )
        {
        if ( OngoingPhysicsAction() == EAknPhysicsActionNone )
            {
            iOngoingAction = EAknPhysicsActionDragging;
            
            if ( iParamProvider->FpsLoggingEnabled() )
                {
                iEngine->StartFpsLogging();
                }
            }

        // reduce movement if content is dragged over boundaries
        if ( OngoingPhysicsAction() == EAknPhysicsActionDragging )
            {
            TInt currentPosition = !iLandscape ? position.iY : position.iX;
            TInt* movementPtr = !iLandscape ? &movement.iY : &movement.iX;
        
            TInt top = viewSize / 2;
            TInt bottom = worldSize - top;
            
            if ( currentPosition < top || currentPosition > bottom )
                {
                *movementPtr /= 2;
                }
            }

        position += movement;
        
        NotifyViewPositionChanged( position, drawNow );
        
        if ( drawNow )
            {
            // Update the CPU time that the null thread has gotten.
            iNullThreadTime = time.Int64();
            }
        }
    }


// ---------------------------------------------------------------------------
// CAknPhysics::SetFriction()
// ---------------------------------------------------------------------------
//
EXPORT_C void CAknPhysics::SetFriction( TReal aFriction )
    {
    if ( iEngine )
        {
        iEngine->SetFriction( aFriction );
        }
    }


// ---------------------------------------------------------------------------
// CAknPhysics::ResetFriction()
// ---------------------------------------------------------------------------
//
EXPORT_C void CAknPhysics::ResetFriction()
    {
    if ( iEngine )
        {
        iEngine->ResetFriction();
        }
    }


// ---------------------------------------------------------------------------
// CAknPhysics::HighlightTimeOut
// ---------------------------------------------------------------------------
//
EXPORT_C TInt CAknPhysics::HighlightTimeout() const
    {
    if ( iParamProvider )
        {
        return iParamProvider->HighlightTimeout();
        }
    return 0;
    }


// ---------------------------------------------------------------------------
// CAknPhysics::DragThreshold
// ---------------------------------------------------------------------------
//
EXPORT_C TInt CAknPhysics::DragThreshold() const
    {
    if ( iParamProvider )
        {
        return iParamProvider->DragThreshold();
        }
    return 0;
    }


// ---------------------------------------------------------------------------
// CAknPhysics::FeatureEnabled
// ---------------------------------------------------------------------------
//
EXPORT_C TBool CAknPhysics::FeatureEnabled()
    {
    return CAknPhysicsParameterProvider::FeatureEnabled();
    }


// ---------------------------------------------------------------------------
// CAknPhysics::EventHandlingAllowed
// ---------------------------------------------------------------------------
//
EXPORT_C TBool CAknPhysics::EventHandlingAllowed() const
    {
    if ( iConeObserver )
        {
        return iConeObserver->EventHandlingAllowed();
        }
    return ETrue;
    }


// ---------------------------------------------------------------------------
// CAknPhysics::UpdateViewWindowControl
// ---------------------------------------------------------------------------
//
EXPORT_C void CAknPhysics::UpdateViewWindowControl( CCoeControl* aControl )
    {
    if ( iConeObserver )
        {
        iConeObserver->UpdateViewWindowControl( aControl );
        }
    }


// ---------------------------------------------------------------------------
// CAknPhysics::SuspendPhysics
// ---------------------------------------------------------------------------
//
EXPORT_C void CAknPhysics::SuspendPhysics()
    {
    // Check that world really exists
    if ( !iEngine || !iEngine->WorldExists() )
        {
        return;
        }
    
    if ( iSuspended )
        {
        // Already suspended
        return;
        }
    iSuspended = ETrue;
    
    iSamePositionCounter = 0;
    if ( iPhysics && iPhysics->IsActive() )
        {
        if ( iParamProvider->FpsLoggingEnabled() )
            {
            iEngine->StopFpsLogging();
            }
        CancelPhysicsTimer();
        }
    else
        {
        UpdateActionState();
        }
    }


// ---------------------------------------------------------------------------
// CAknPhysics::ResumePhysics
// ---------------------------------------------------------------------------
//
EXPORT_C void CAknPhysics::ResumePhysics()
    {
    if ( !iEngine || !iEngine->WorldExists() )
        {
        return;
        }
    
    if ( !iSuspended )
        {
        // Not suspended
        return;
        }
    iSuspended = EFalse;
    
    if ( iParamProvider->FpsLoggingEnabled() )
        {
        iEngine->StopFpsLogging();
        }

    iEngine->SetViewBodyPosition( iObserver.ViewPosition() );
    StartPhysics();         
            
    if ( iParamProvider->FpsLoggingEnabled() )
        {
        iEngine->StartFpsLogging();
        }
    }


// ---------------------------------------------------------------------------
// CAknPhysics::UpdateViewPosition
// ---------------------------------------------------------------------------
//
void CAknPhysics::UpdateViewPosition()
    {
    if ( iOngoingAction == CAknPhysics::EAknPhysicsActionNone
         || iOngoingAction == CAknPhysics::EAknPhysicsActionDragging )
        {
        TPoint viewPosition( iObserver.ViewPosition() );
        if ( iRestrictor 
            && iRestrictor->PositionRevealsEmptySpace( viewPosition ) )
            {
            NotifyViewPositionChanged( viewPosition );            
            }
        }
    }


// ---------------------------------------------------------------------------
// CAknPhysics::Landscape
// ---------------------------------------------------------------------------
//
const TBool& CAknPhysics::Landscape()
    {
    return iLandscape;
    }


// ---------------------------------------------------------------------------
// CAknPhysics::ViewSize
// ---------------------------------------------------------------------------
//
const TSize& CAknPhysics::ViewSize()
    {
    return iViewSize;
    }


// ---------------------------------------------------------------------------
// CAknPhysics::WorldSize
// ---------------------------------------------------------------------------
//
const TSize& CAknPhysics::WorldSize()
    {
    return iWorldSize;
    }


// ---------------------------------------------------------------------------
// CAknPhysics::SurfaceErp
// ---------------------------------------------------------------------------
//
TInt CAknPhysics::SurfaceErp() const
    {
    if ( iParamProvider )
        {
        return iParamProvider->SurfaceErp();
        }
    return 0;
    }


// ---------------------------------------------------------------------------
// CAknPhysics::SurfaceCfm
// ---------------------------------------------------------------------------
//
TInt CAknPhysics::SurfaceCfm() const
    {
    if ( iParamProvider )
        {
        return iParamProvider->SurfaceCfm();
        }
    return 0;
    }

// ---------------------------------------------------------------------------
// CAknPhysics::IsPanningDrawIgnored
// ---------------------------------------------------------------------------
//
TBool CAknPhysics::IsPanningDrawOmitted() const
    {
    return iPanningDrawOmitted;
    }



// ---------------------------------------------------------------------------
// CAknPhysics::FrameDelay
// ---------------------------------------------------------------------------
//
TInt CAknPhysics::FrameDelay() const
    {
    if ( iParamProvider )
        {
        return iParamProvider->FrameDelay();
        }

    return KPhysicsMinTime / 1000;
    }


// ---------------------------------------------------------------------------
// CAknPhysics::MinFrameInterval
// ---------------------------------------------------------------------------
//
TInt CAknPhysics::MinFrameInterval() const
    {
    if ( iParamProvider )
        {
        return iParamProvider->MinFrameInterval();
        }

    return KPhysicsTime / 1000;
    }


// ---------------------------------------------------------------------------
// CAknPhysics::DrawViewInCurrentPosition
// ---------------------------------------------------------------------------
//
void CAknPhysics::DrawViewInCurrentPosition()
    {
    NotifyViewPositionChanged( iObserver.ViewPosition(), ETrue );
    iPanningDrawOmitted = EFalse;
    }

// --------------------------------------------------------------------------
// CAknPhysics::SetBounceTactileFeedback
// --------------------------------------------------------------------------
//
void CAknPhysics::SetBounceTactileFeedback( TBool aBounceTactileFeedback )
    {
    iBounceTactileFeedback = aBounceTactileFeedback;
    }

// ---------------------------------------------------------------------------
// CAknPhysics::CAknPhysics
// ---------------------------------------------------------------------------
//
CAknPhysics::CAknPhysics( MAknPhysicsObserver& aObserver )
    : iObserver( aObserver ),
    iOngoingAction( EAknPhysicsActionNone ),
    iParamProvider( NULL ),
    iRestrictor( NULL ),
    iConeObserver( NULL ),
    iEngine( NULL ),
    iNullThreadTime( 0 ),
    iTimeOfLastDraw( 0 ), 
    iBounceTactileFeedback( ETrue ),
    iPanningDrawOmitted( EFalse )
    {
    }


// ---------------------------------------------------------------------------
// CAknPhysics::ConstructL()
// ---------------------------------------------------------------------------
//
void CAknPhysics::ConstructL( CCoeControl* aViewControl )
    {
    // Open the handle to the null thread.
    TBool nullThreadOpen = FindNullThread( iNullThread );
    
    // Create Physics timer to step physics emulation
    iPhysics = CAknHighResPeriodic::NewL(
        CActive::EPriorityStandard, nullThreadOpen ? &iNullThread : NULL );
    
    iEngine = CAknPhysicsEngine::NewL( this );
    iParamProvider = CAknPhysicsParameterProvider::NewL();
    iRestrictor = CAknPhysicsRestrictor::NewL( iParamProvider );
    iConeObserver = CAknPhysicsConeObserver::NewL( this, iRestrictor );
    iConeObserver->SetViewWindowControl( aViewControl );
    iFeedback = MTouchFeedback::Instance();
    iPhysics->SetMinCallBackPeriod( FrameDelay() * 1000 );
    }


// ---------------------------------------------------------------------------
// CAknPhysics::StartPhysics()
// ---------------------------------------------------------------------------
//
void CAknPhysics::StartPhysics()
    {
    // Check that world really exists
    if ( !iEngine || !iEngine->WorldExists() )
        {
        return;
        }

    iEngine->ResetCollisionState();
    iEngine->EnableViewBody();
    CancelPhysicsTimer();
    if ( iPhysics && !iPhysics->IsActive() )
        {
        iPhysics->Start(
                0, KPhysicsTime, TCallBack( CAknPhysics::Simulate, this ) );
		UpdateActionState();
        }
    }


// ---------------------------------------------------------------------------
// CAknPhysics::ApplyDrag()
// ---------------------------------------------------------------------------
//
void CAknPhysics::ApplyDrag( TPoint aDrag, TInt aMoveTime )
    {
    // Check that world really exists
    if ( !iEngine || !iEngine->WorldExists() )
        {
        return;
        }
    
    if ( iRestrictor )
        {
        iRestrictor->AdjustDragPoint( aDrag );
        }

    iEngine->ResetCollisionState();

    if(iLandscape)
        {
        if(aDrag.iY > iViewSize.iHeight/2 || aDrag.iY < -iViewSize.iHeight/2)
            aDrag.iX = 0;
        else
            aDrag.iY = 0;
        }
    else
        {
        if(aDrag.iX > iViewSize.iWidth/2 || aDrag.iX < -iViewSize.iWidth/2)
            aDrag.iY = 0;
        else
            aDrag.iX = 0;
        }

    if( aMoveTime < KSwipeDuration )
        {
        iEngine->ApplyDragForce( aDrag, aMoveTime );
        }
    }


// ---------------------------------------------------------------------------
// CAknPhysics::Simulate()
// ---------------------------------------------------------------------------
//
TInt CAknPhysics::Simulate(TAny* aSelf)
    {
    CAknPhysics* self = (CAknPhysics*) aSelf;
    self->DoSimulation();
    return KErrNone;
    }


// ---------------------------------------------------------------------------
// CAknPhysics::DoSimulation()
// ---------------------------------------------------------------------------
//
void CAknPhysics::DoSimulation()
    {
    if ( iFlickTimeExceeded && iOngoingAction != EAknPhysicsActionBouncing  )
        {
        iFlickTimeExceeded = EFalse;
        StopPhysics();
        return;
        }

    TPoint previousViewPosition;
    TPoint currentViewPosition;

    // get position before step
    iEngine->GetViewBodyPosition( previousViewPosition );
    
    CAknPhysicsEngine::TAknPhysicsCollision prevState = iEngine->CollisionState();

    iEngine->TakePhysicsStep();

    // get position after step
    iEngine->GetViewBodyPosition( currentViewPosition );
    
    // Signal physics stop only after a possible view position change has been 
    // communicated.
    TBool stopPhysics = EFalse;
    
    // if iViewBody gets auto disabled, it's safe to stop physics emulation 
    if( !iEngine->IsViewBodyEnabled() )
        {
        stopPhysics = ETrue;
        }
    else if ( prevState != CAknPhysicsEngine::EAknPhysicsNoCollision )
        {

        TBool collisionChanged( 
            iEngine->CollisionState() != CAknPhysicsEngine::EAknPhysicsNoCollision 
            && iEngine->CollisionState() != prevState );
        TBool topCollision( prevState == CAknPhysicsEngine::EAknPhysicsTopCollision );
        TBool previousOut( EFalse );
        TBool currentOut( EFalse );

        // Check if positions are out of boundaries
        if ( iRestrictor )
            {
            previousOut = iRestrictor->PositionIsOutOfBoundaries( 
                previousViewPosition, topCollision );
            currentOut = iRestrictor->PositionIsOutOfBoundaries( 
                currentViewPosition, topCollision );
            }

        // Stop bounce if
        // 1) Collision has changed from top to bottom or bottom to top
        // 2) Current view position is out of boundaries (and previous was not)
        // -> prevent view flicking when bounce ends
        // 3) Collision type has changed to no collision and both of the view
        // positions are inside view boundaries.
        if ( collisionChanged 
            || currentOut && !previousOut
            || !currentOut && !previousOut 
            && iEngine->CollisionState() == CAknPhysicsEngine::EAknPhysicsNoCollision )
            {
            if ( topCollision || collisionChanged )
                {
                iRestrictor->PositionToViewTop( currentViewPosition );
                }
            else
                {
                iRestrictor->PositionToViewBottom( currentViewPosition );
                }
            stopPhysics = ETrue;
            iEngine->ResetCollisionState();
            // boundary effect                       
            if ( iBounceTactileFeedback && iFeedback )
                {
                iFeedback->InstantFeedback( NULL,
                                            ETouchFeedbackBoundaryList,
                                            ETouchFeedbackVibra,
                                            TPointerEvent() );
                }
            }

        // Previous view position and current view position are out of view
        // boundaries -> bounce has changed to flick
        else if ( currentOut && previousOut )
            {
            iEngine->ResetCollisionState();
            }
        }

    UpdateActionState();

    if ( currentViewPosition == previousViewPosition )
        {
        iSamePositionCounter++;
        // Never stop physics while bouncing even though
        // same position count has been exceeded
        if ( iOngoingAction != EAknPhysicsActionBouncing 
            && iSamePositionCounter >= KSamePositionStopCount )
            {
            stopPhysics = ETrue;
            }
        }

    NotifyViewPositionChanged( currentViewPosition );
       
    if ( stopPhysics )
        {
        StopPhysics();
        }
    }


// ---------------------------------------------------------------------------
// CAknPhysics::CancelPhysicsTimer()
// ---------------------------------------------------------------------------
//
void CAknPhysics::CancelPhysicsTimer()
    {
    if ( iPhysics )
        {
        iPhysics->Cancel();
        UpdateActionState();
        }
    }


// ---------------------------------------------------------------------------
// CAknPhysics::UpdateActionState()
// ---------------------------------------------------------------------------
//
void CAknPhysics::UpdateActionState()
    {
    if ( iPhysics && iPhysics->IsActive() )
        {
        // If we are colliding or physics timer is active and view is about
        // to bounce -> action to bouncing
        if ( iEngine->CollisionState() != CAknPhysicsEngine::EAknPhysicsNoCollision
            || ( iOngoingAction == EAknPhysicsActionNone && ViewAboutToBounce() ) )
            {
            iOngoingAction = EAknPhysicsActionBouncing;
            }
        // Otherwise we are flicking
        else
            {
            iOngoingAction = EAknPhysicsActionFlicking;
            }
        }
    else
        {
        iOngoingAction = EAknPhysicsActionNone;
        }

    if ( iConeObserver )
        {
        iConeObserver->PhysicsStateChanged();
        }
    }


// ---------------------------------------------------------------------------
// CAknPhysics::ViewAboutToBounce()
// ---------------------------------------------------------------------------
//
TBool CAknPhysics::ViewAboutToBounce()
    {
    TPoint viewPosition( iObserver.ViewPosition() );
    if ( iRestrictor && iRestrictor->PositionRevealsEmptySpace( viewPosition ) )
        {
        return ETrue;
        }
    return EFalse;
    }


// ---------------------------------------------------------------------------
// CAknPhysics::NotifyViewPositionChanged
// ---------------------------------------------------------------------------
//
void CAknPhysics::NotifyViewPositionChanged( const TPoint& aPosition,
                                             TBool aDrawNow )
    {
    iObserver.ViewPositionChanged( aPosition, aDrawNow, 0 );
    
    if ( aDrawNow )
        {
        // Update the time of the last draw.
        iTimeOfLastDraw.UniversalTime();
        }
    }

// End of File
