tactilefeedback/tactilefeedbackclient/src/touchfeedbackclient.cpp
author Pat Downey <patd@symbian.org>
Wed, 01 Sep 2010 12:23:25 +0100
branchRCL_3
changeset 37 09b094b73eb8
parent 36 39d4c97df8cb
child 46 df6ad9bd4687
permissions -rw-r--r--
Revert incorrect RCL_3 drop: Revision: 201033 Kit: 201035

/*
* 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:  Communication with server side implementation of
*                Area Registry.
* Part of:      Tactile Feedback.
*
*/

#include <e32std.h>
#include <eikenv.h>

#include <tactileinternaldatatypes.h>
#include <tactilefeedbacktrace.h>

#include "touchfeedbackclient.h"
#include "touchfeedbackimpl.h"
#include "touchfeedbackregistry.h"
#include "OstTraceDefinitions.h"
#ifdef OST_TRACE_COMPILER_IN_USE
#include "touchfeedbackclientTraces.h"
#endif

// Minimun and maximum sized of one shared memory chunk.
const TInt KTactileChunkInitialSize = 4096; // 4kB
const TInt KTactileChunkMaxSize     = 262144; // 256kB
const TUid KTactileClickPluginUid   = { 0x2000B493 };

_LIT( KChunkNameFormat, "PID_0x%Lx_TID_0x%Lx" );
// ======== MEMBER FUNCTIONS ========

// ---------------------------------------------------------------------------
// 
// ---------------------------------------------------------------------------
//
CTouchFeedbackClient::CTouchFeedbackClient( CTouchFeedbackImpl& aFeedback ):
    iClickPlugin ( CEikonEnv::Static()->WsSession()),
    iFeedback( aFeedback )
    {
    }

// ---------------------------------------------------------------------------
// We do all the initializations in the construction phase. Other option
// would be to postpone these until the first registry entry has been added.
//
// #1 Connect to our ClickPlugin
// #2 Create a shared chunk, which is used for storing this application
//    process' area registry, so that it can be accessed by the window
//    server.
// #3 Set window count to zero in the new chunk
// #4 Try to create a global semaphore to have mutual exclusion on chunks
//    If semaphore already exists, then try to open it.
// #5 Put our chunk name to package buffer, and send it to click plugin
//    so that it can open a handle to our chunk and thus access the shared
//    memory.
// #6 Create a CIdle object that is used for updating the registry in shared
//    memory.
//
// Notice that we will leave in case anything fails. This has to be handled
// on higher level, so that whole application launch will not fail in case
// touch feedback does not work.
// ---------------------------------------------------------------------------
//
void CTouchFeedbackClient::ConstructL()
    {
    TRACE("CTouchFeedbackClient::ConstructL - Begin");
    
    // #1 Connect to click plugin
    User::LeaveIfError( iClickPlugin.Construct( KTactileClickPluginUid ) );
        
    TBool changable = EFalse;
    
    TBool loaded = iClickPlugin.IsLoaded( changable );  
    
    if ( !loaded )
        {
        User::Leave( KErrNotSupported );
        }

    // #2 Create shared chunk 
    TTactileFeedbackConnectData data;
    
    // Set window group identifier
    data.iWindowGroupId = CCoeEnv::Static()->RootWin().Identifier();
           
    // We use our own process id and thread id as name for the chunk
    data.iChunkName.Format( KChunkNameFormat,
                            RProcess().Id().Id(), RThread().Id().Id() );
    
    // Now create the chunk
    TInt err = iChunk.CreateGlobal( data.iChunkName, 
                                    KTactileChunkInitialSize, 
                                    KTactileChunkMaxSize );     
        
    // We have to take into account, that the chunk may already exist.
    // This may happen in case we have been started (and crashed)
    // once already.
    if ( err == KErrAlreadyExists )
        {
        User::LeaveIfError( iChunk.OpenGlobal( data.iChunkName, EFalse ) );
        }
    else if( err != KErrNone )
        {
        User::LeaveIfError( err );
        }
    
    // #3 Set window count to zero
    TInt* chunkPtr = reinterpret_cast<TInt*>( iChunk.Base() );
    
    *chunkPtr = 0; // No entries in the chunk in the beginning
    
    // #4 Try to create/open global semaphore
    err = iSemaphore.CreateGlobal( KTouchFeedbackSemaphore, 1 );
    
    if ( err == KErrAlreadyExists )
        {
        User::LeaveIfError( iSemaphore.OpenGlobal( KTouchFeedbackSemaphore ) );
        }
    else if( err != KErrNone )
        {
        User::LeaveIfError( err );
        }
       
    // #5 Send chunk name to window server
    TPckgC<TTactileFeedbackConnectData> dataPkg ( data );
    
    User::LeaveIfError( iClickPlugin.CommandReply( ETactileOpCodeConnect, 
                                                   dataPkg ) );
    iConnected = ETrue;
    
    iFeedbackTimer = CPeriodic::NewL( CActive::EPriorityStandard );
        
    User::LeaveIfError( iFbClient.Connect() );
            
    // #6 We use Standard priority so that we won't disturb the normal 
    //    functionality of the application, but can get registry updated
    //    fastly enough in case the application does very plenty of 
    //    background prosessing with active objects.
    iIdle = CIdle::NewL( CActive::EPriorityStandard );
    
    TRACE("CTouchFeedbackClient::ConstructL - End");
    }

// ---------------------------------------------------------------------------
// 
// ---------------------------------------------------------------------------
//
CTouchFeedbackClient* CTouchFeedbackClient::NewL( CTouchFeedbackImpl& aFeedback )
    {
    CTouchFeedbackClient* self = 
        new( ELeave ) CTouchFeedbackClient ( aFeedback );
    CleanupStack::PushL( self );
    self->ConstructL();    
    CleanupStack::Pop( self );
    return self;
    }

// ---------------------------------------------------------------------------
// 
// ---------------------------------------------------------------------------
//
CTouchFeedbackClient::~CTouchFeedbackClient()
    {
    iChunk.Close();
    iSemaphore.Close();

    if ( iFeedbackTimer )
        {
        iFeedbackTimer->Cancel();
        delete iFeedbackTimer;    
        }
    
    iFbClient.Close();
   
    if ( iConnected )
        {
        TTactileFeedbackDisconnectData data;
        
        data.iWindowGroupId = CCoeEnv::Static()->RootWin().Identifier();
        
        TPckgC<TTactileFeedbackDisconnectData> dataPkg ( data );
    
        iClickPlugin.CommandReply( ETactileOpCodeDisconnect, dataPkg );    
        }
    
    iClickPlugin.Close();  
    
    delete iIdle;  
    }
   
    
// ---------------------------------------------------------------------------
// In this function we just activate CIdle. The motivation is that this
// way we avoid updating the registry multiple times. For e.g. Calculator
// application launch would cause around 20 registry updates in case we would
// always make changes to shared memory immediately after registry update.
// ---------------------------------------------------------------------------
//
void CTouchFeedbackClient::RegistryChanged( )
    {
    if ( iIdle && !iIdle->IsActive() )
        {
        iIdle->Start( TCallBack( IdleCallbackL, this ) ); 
        }
    }

// ---------------------------------------------------------------------------
// Callback for CIdle.
//
// We check the current enabled/disabled status of tactile feedback for
// this application, and either UpdateRegistryToChunkL for updating whole
// registry to shared chunk, or ClearWholeChunkL for clearing all feedback
// areas from the shared chunk.
// ---------------------------------------------------------------------------
//
TInt CTouchFeedbackClient::IdleCallbackL( TAny* aPtr )
    {
    if ( aPtr )
        {
        CTouchFeedbackClient* self =
            static_cast<CTouchFeedbackClient*> ( aPtr );

        if ( self->iFeedback.FeedbackEnabledForThisApp() )
            {
            self->UpdateRegistryToChunkL();
            }
        else
            {
            self->ClearWholeChunkL();
            }
        }
    
    return KErrNone;
    }

// ---------------------------------------------------------------------------
// Area registry is written to shared memory in following format
//
// + Header (four bytes) = Number of windows as TInt
// + Window entries 
// + Area registry entries
// 
// Window entries: For each window:
//   [four bytes] = Window handle 
//   [four bytes] = Number of registry entries in this window 
//   [four bytes] = Offset of registry data from beginning of chunk
// Area registry entries:
//   - Each entry written as TFeedbackChunkAreaEntry straight into memory
//
//
// Steps taken in this function:
//
// #1 Calculate the size needed for area registry data
// #2 Mutual exclusion with global semaphore
// #3 Increase Chunk size in case necessary
// #4 Update Chunk contents. This is now done simply by writing the whole
//    area registry into the shared memory.
// #5 Release semaphore
// ---------------------------------------------------------------------------
//
void CTouchFeedbackClient::UpdateRegistryToChunkL()
    {
    TRACE( "CTouchFeedbackClient::UpdateRegistryToChunkL - Begin" );
    
    RPointerArray<CTouchFeedbackRegistry>* registry =
        iFeedback.RegistryArray();

    // Do cleanup so that empty registry instances are not hanging anymore
    TInt windowIndex = 0;
    TInt cleanupAreaCount = 0;
    TInt cleanupWindowCount = 0;
    // value is returned in parameter variable  
    iFeedback.GetAreaCount( cleanupAreaCount, cleanupWindowCount );  
    
    while ( windowIndex < cleanupWindowCount )
        {
        TBool emptyWindow = ( (*registry)[windowIndex]->AreaCount() == 0 ); 
        if ( emptyWindow )
            {
            // Remove empty/possibly destroyed registry instance that possibly 
            // may refer to destroyed control/window.
            CTouchFeedbackRegistry* feedbackRegistry = (*registry)[windowIndex];
            registry->Remove( windowIndex );
            delete feedbackRegistry;
            cleanupWindowCount--;
            }
        else
            {
            windowIndex++; // to next window
            }
        }
        
    // #1 Calculate the size needed    
    TInt windowCount(0);
    TInt areaCount(0);
    iFeedback.GetAreaCount( areaCount, windowCount );    
        
    // Header contains only number of windows in this registry
    TInt headerSize = sizeof( TInt );     
        
    // There is a list of windows in this registry in the beginning, with
    // data offsets in the chunk    
    TInt windowItemSize = 
        sizeof( TInt ) + // Window handle
        sizeof( TInt ) + // Area count
        sizeof( TInt );  // Offset to area registry entries
    
    // Size of one area registry entry
    TInt areaItemSize = sizeof( TFeedbackChunkAreaEntry );    
           
    TInt sizeNeeded =     
        headerSize + 
        windowCount * windowItemSize + 
        areaCount * areaItemSize;
        
    // #2 Mutual exclusion    
    iSemaphore.Wait();

     // We use cleanup item for making sure that semaphore will not stay
    // reserved in case there is a leave.
    CleanupStack::PushL( TCleanupItem( CleanupSemaphore, &iSemaphore ) );
       
    // #3 Increase chunk (committed) size in case needed    
    if ( iChunk.Size() < sizeNeeded )
        {
        TInt err = iChunk.Adjust( sizeNeeded );
        
        if ( err != KErrNone )
            {
            // In case the current registry does not fit to the chunk, then
            // we'll set window count to zero, in case that is possible
            // (it is not possible if chunk size is zero).
            // This means that there won't be any area registry based 
            // feedback for this application on the moment.
            if ( iChunk.Size() > 0 )
                {
                TInt* chunkPtr = reinterpret_cast<TInt*>( iChunk.Base() );
                    
                // Set window count to zero   
                *chunkPtr = 0;           
                }
                
            // We will leave anyway
            User::Leave( err );
            }
        }
        
        
    // #4 Update chunk contents    
    TInt* chunkPtr = reinterpret_cast<TInt*>( iChunk.Base() );
        
    // Set window count in the beginning of chunk    
    *chunkPtr = windowCount; 
    chunkPtr++;
            
    TInt areaOffset = headerSize + windowCount * windowItemSize;
    
    // This is pointer to the first area registry entry in the chunk
    TFeedbackChunkAreaEntry* chunkEntryPtr 
       = reinterpret_cast<TFeedbackChunkAreaEntry*> ( iChunk.Base() + areaOffset );
    
    // Check if audio or vibra is disabled for this application.
    TBool audioEnabled = iFeedback.FeedbackEnabledForThisApp( ETouchFeedbackAudio );
    TBool vibraEnabled = iFeedback.FeedbackEnabledForThisApp( ETouchFeedbackVibra );
            
    // One loop round for each window where we have area registry entries.
    for ( TInt windowIndex = 0; windowIndex < windowCount; windowIndex++ )
        {
        RArray<TFeedbackEntry>* windowRegistry = 
            (*registry)[windowIndex]->WindowRegistry();
        
        // Write handle of this window
        *chunkPtr = (*registry)[windowIndex]->WindowHandle();
        chunkPtr++;
        // Store the address where we shall write count later
        // (We don't know the amount of areas yet, as we shall
        // only add areas of visible controls).
        TInt* countPointer = chunkPtr;
        chunkPtr++;
        // Write offset of the area registry entries of this window
        *chunkPtr = ((TInt)chunkEntryPtr-(TInt)iChunk.Base());
        chunkPtr++;
        
        // Write all areas of visible controls in this window 
        // to the shared memory. Invisible controls' areas are left out so
        // that they won't mess up the feedback for overlapping visible
        // controls.
        TInt visibleAreaCount = 0;
        for ( TInt areaIndex = windowRegistry->Count()-1; areaIndex >= 0; areaIndex-- )
            {
            TFeedbackEntry entry = (*windowRegistry)[areaIndex];
            
            if ( entry.iVisible ) 
                {
            chunkEntryPtr->iRect = entry.iRect;
            
            TInt feedback = ( entry.iFeedbackTypeUp << 10 );
            feedback |= entry.iFeedbackTypeDown;            

            // Add audio and vibra information to feedback type
            if ( entry.iVibraEnabled && vibraEnabled )
                {
                //feedback |= KTactileVibraBit;    
                if ( entry.iFeedbackDown & ETouchFeedbackVibra )
                    {
                    feedback |= KTactileVibraBitDown;    
                    }
                if ( entry.iFeedbackUp & ETouchFeedbackVibra )
                    {
                    feedback |= KTactileVibraBitUp;    
                    }
                
                }
            if ( entry.iAudioEnabled && audioEnabled )
                {
                //feedback |= KTactileAudioBit;
                if ( entry.iFeedbackDown & ETouchFeedbackAudio )
                    {
                    feedback |= KTactileAudioBitDown;    
                    }
                if ( entry.iFeedbackUp & ETouchFeedbackAudio )
                    {
                    feedback |= KTactileAudioBitUp;    
                    } 
                }
                
            chunkEntryPtr->iFeedbackType = static_cast<TTouchLogicalFeedback>( feedback );
            
            chunkEntryPtr->iEventType    = entry.iEventType;    
            
            chunkEntryPtr++;        
                visibleAreaCount++;      
                }            
            }
        // Now store area count as we know the amount of visible areas
        // for this window.    
        *countPointer = visibleAreaCount;     
        }   
        
    // #5 Release semaphore so that other processes can access the chunks again    
    // This calls "Signal" on the semaphore
    CleanupStack::PopAndDestroy(&iSemaphore);

    TRACE( "CTouchFeedbackClient::UpdateRegistryToChunkL - End" );
    }

// ---------------------------------------------------------------------------
// Despite the function name, we don't actually clear whole chunk here
// as that is not necessary. It is enough to set the window count to zero
// at beginning of chunk, as then server will consider this chunk empty.
//
// #1 Mutual exclusion
// #2 Make sure chunk is not of zero -size
// #3 Set window count to zero at beginning of chunk
// #4 Release mutual exclusion
// ---------------------------------------------------------------------------
//
void CTouchFeedbackClient::ClearWholeChunkL()
    {
    TRACE( "CTouchFeedbackClient::ClearWholeChunkL - Begin" );
    
    // #1 Mutual exclusion    
    iSemaphore.Wait();
    
    // We use cleanup item for making sure that semaphore will not stay
    // reserved in case there is a leave.
    CleanupStack::PushL( TCleanupItem( CleanupSemaphore, &iSemaphore ) );
    
    // We only need four bytes for the data, as we are only going to store
    // one number to the chunk. But we count the space just in case anyway, 
    // because at some point chunks initial size may be change so that it is
    // zero.
    TInt sizeNeeded = sizeof( TInt );
        
    // #2 Increase chunk (committed) size in case needed    
    if ( iChunk.Size() < sizeNeeded )
        {
        User::LeaveIfError( iChunk.Adjust( sizeNeeded ) );
        }
  
      // #3 Update chunk contents    
    TInt* chunkPtr = reinterpret_cast<TInt*>( iChunk.Base() );

    // Set window count to zero (this is enough for the server to think
    // that there are no feedback areas at all).
    *chunkPtr = 0; 

    // #4 Release semaphore
    CleanupStack::PopAndDestroy(&iSemaphore);         
    
    TRACE( "CTouchFeedbackClient::ClearWholeChunkL - End" );
    }

// ---------------------------------------------------------------------------
// This is a cleanup function for releasing a semaphore in case there will
// be a leave after calling RSemaphore::Wait (otherwise there could easily
// be a deadlock in the system).
// ---------------------------------------------------------------------------
//
void CTouchFeedbackClient::CleanupSemaphore( TAny* aPtr )
    {
    TRACE( "CTouchFeedbackClient::CleanupSemaphore" );
    
    if ( aPtr )
        {
        RSemaphore* sem = static_cast<RSemaphore*> ( aPtr );
        
        sem->Signal();
        }    
    }

// ---------------------------------------------------------------------------
// Here we just send the logical feedback type to server.
// ---------------------------------------------------------------------------
//
void CTouchFeedbackClient::ImmediateFeedback( TTouchLogicalFeedback aType,  
    TBool aVibraOn, TBool aAudioOn  )
    {
    OstTrace0( TACTILE_PERFORMANCE, TACTILE_CLIENT_INSTANT_FEEDBACK_1, "e_TACTILE_CLIENT_INSTANT_FEEDBACK 1");
    
    if ( aVibraOn || aAudioOn )
        {
        iFbClient.PlayFeedback( aType, aVibraOn, aAudioOn );
        }
    
    OstTrace0( TACTILE_PERFORMANCE, TACTILE_CLIENT_INSTANT_FEEDBACK_0, "e_TACTILE_CLIENT_INSTANT_FEEDBACK 0");
    }

// ---------------------------------------------------------------------------
// Check that we really have something to update, and then do the update.
// ---------------------------------------------------------------------------
//
void CTouchFeedbackClient::FlushUpdates()
    {
    if ( iIdle && iIdle->IsActive() )
        {
        // Do the updates
        TRAP_IGNORE( IdleCallbackL( this ) );
        
        // Cancel pending update request (no need to keep it active since
        // registry is now up-to-date).
        iIdle->Cancel();
        }
    }
    
// ----------------------------------------------------------------------------
// 
// ----------------------------------------------------------------------------
//
void CTouchFeedbackClient::StartFeedback( TUint32 aClientHandle,
                                          TTouchContinuousFeedback aType,
                                          TInt aIntensity,
                                          TTimeIntervalMicroSeconds32 aTimeout )
    {
    TRACE("CTouchFeedbackClient::StartFeedback - Begin");

    if ( !iFeedbackTimer->IsActive() )
        {
        iPreviousIntensity = aIntensity;
        iFbClient.StartFeedback( aType, aIntensity );
        iClientHandle = aClientHandle; // Control, which started the feedback.
        }
    if ( aTimeout != TTimeIntervalMicroSeconds32(0) )
        {
        StartFeedbackTimer( aTimeout );
        
        if ( aIntensity != iPreviousIntensity )
            {
            ModifyFeedback( aClientHandle, aIntensity );
            }
        }
    
    TRACE("CTouchFeedbackClient::StartFeedback - End");
    }
    
// ----------------------------------------------------------------------------
// 
// ----------------------------------------------------------------------------
//
void CTouchFeedbackClient::ModifyFeedback( TUint32 aClientHandle, 
                                           TInt aIntensity )
    {
    // Modification is allowed only for the same control, which started 
    // the feedback.
    if ( aClientHandle == iClientHandle &&
         aIntensity != iPreviousIntensity )
        {
        iPreviousIntensity = aIntensity;
        iFbClient.ModifyFeedback( aIntensity );    
        }    
    }
                             
// ----------------------------------------------------------------------------
// 
// ----------------------------------------------------------------------------
//
void CTouchFeedbackClient::StopFeedback( const TUint32 aClientHandle )
    {
    TRACE("CTouchFeedbackClient::StopFeedback - Begin");
    if ( aClientHandle == iClientHandle )
        {
        iFbClient.StopFeedback();
        iFeedbackTimer->Cancel();
        // Clear also client handle to indicate there's no ongoing feedback.
        iClientHandle = 0; 
        }
    else if ( !iClientHandle ) // Stop instant feedback
        {
        iFbClient.StopFeedback();
        }
    TRACE("CTouchFeedbackClient::StopFeedback - End");
    }    

// ---------------------------------------------------------------------------
// 
// ---------------------------------------------------------------------------
//
TInt CTouchFeedbackClient::SetFeedbackEnabledForDevice( TTouchFeedbackType aFeedbackType )
    {
    return iFbClient.SetFeedbackEnabledForDevice( aFeedbackType );
    }

// ----------------------------------------------------------------------------
// 
// ----------------------------------------------------------------------------
//
TTouchFeedbackType CTouchFeedbackClient::FeedbackEnabledForDevice()
    {
    TTouchFeedbackType feedbackEnabled;
    iFbClient.FeedbackEnabledForDevice( feedbackEnabled );
    return feedbackEnabled;
    }

// ----------------------------------------------------------------------------
// 
// ----------------------------------------------------------------------------
//
void CTouchFeedbackClient::StartFeedbackTimer( TTimeIntervalMicroSeconds32 aTimeout )
    {
    iFeedbackTimer->Cancel();
    TCallBack callback( StopFeedbackCallback, this );
    iFeedbackTimer->Start( aTimeout, 0, callback );
    }    

// ----------------------------------------------------------------------------
// 
// ----------------------------------------------------------------------------
//
void CTouchFeedbackClient::StopFeedbackByTimeout()
    {
    iFeedbackTimer->Cancel();
    iFbClient.StopFeedback();
    iClientHandle = 0; 
    }

// ----------------------------------------------------------------------------
// CAknSlider::StopFeedbackCallback
// ----------------------------------------------------------------------------
//
TInt CTouchFeedbackClient::StopFeedbackCallback( TAny* aThis )
    {
    TRACE("CTouchFeedbackClient::StopFeedbackCallback - Begin");
    static_cast<CTouchFeedbackClient*>( aThis )->StopFeedbackByTimeout();
    TRACE("CTouchFeedbackClient::StopFeedbackCallback - End");
    return KErrNone;
    }    

// End of File