javauis/eswt_qt/org.eclipse.swt/Eclipse_SWT_PI/qt/library/swts60.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Tue, 06 Jul 2010 14:10:26 +0300
changeset 48 e0d6e9bd3ca7
parent 35 85266cc22c7f
child 50 023eef975703
permissions -rw-r--r--
Revision: v2.2.3 Kit: 2010127

/*******************************************************************************
 * Copyright (c) 2009, 2010 Nokia Corporation and/or its subsidiary(-ies).
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Nokia Corporation - initial implementation
 *******************************************************************************/

#include <eikenv.h>
#include <apgwgnam.h> // For CApaWindowGroupName
#include <coemain.h>
#include <eikappui.h>
#include <w32std.h>
#include <avkon.hrh>
#include <QVariant>
#include <QWidget>
#include <AknDef.h>
#include <apgtask.h>
#include <hal.h>
#include <org_eclipse_swt_internal_qt_OS.h>
#include "eventcallback.h"
#include "swts60.h"
#include "autorelease.h"

using namespace Java::eSWT;

// Name of the UI thread.
_LIT(KSwtUiThreadName, "SwtQtUiThread");

// Name of the support thread.
_LIT(KSwtSupportThreadName, "SwtQtSupportThread");

// Name of the shared library
_LIT(KSwtDllName, "eswtqt.dll");

// Stack size for the UI thread, 0x14000 = 80kB
const TInt KSwtUiThreadStackSize = 0x14000;

// Stack size for the support thread, 0x1F40 = 8kB
const TInt KSwtSupportThreadStackSize = 0x1F40;

static const char* const EVENT_FILTER = "swt_event_filter";

// Data stored to thread/dll specific Symbian thread local storage of the UI
// thread.
typedef struct
    {
    JavaVM* vm;
    jint uid;
    jobject runner;
    TRequestStatus initStatus;
    TThreadId initThreadId;
    TThreadId uiThreadId;
    TBool doFreeTLSInCleanupUIThread;
    }
SwtTlsData;

inline static void freeTLSData(SwtTlsData*& aData)
    {
    Dll::SetTls(NULL);
    delete aData;
    aData = NULL;
    }

TUid SwtQtS60MainApplication::AppDllUid() const
    {
    SwtTlsData* data = reinterpret_cast<SwtTlsData*>(Dll::Tls());
    TUid uid;
    uid.iUid = static_cast<TInt>(data ? data->uid : 0);
    return uid;
    }

int SymbianUtils::startUI(JNIEnv* aJniEnv, jobject aRunner, jint aUid)
    {
    // Get a VM pointer using the current thread context
    JavaVM* javaVM = NULL;
    jint getVMStatus = aJniEnv->GetJavaVM(&javaVM);
    if(getVMStatus < 0) return KErrNoMemory;

    // Add a global reference to callback object so that in can be used from
    // the UI thread.
    jobject globalRef = aJniEnv->NewGlobalRef(aRunner);
    if(!globalRef) return KErrNoMemory;

    // Put needed data to a stucture for TLS storing that will be done later
    // in the UI thread
    SwtTlsData* data = new (std::nothrow) SwtTlsData();
    if(!data) return KErrNoMemory;
    data->vm = javaVM;
    data->uid = aUid;
    data->runner = globalRef;
    data->initStatus = KRequestPending;
    RThread thread;
    data->initThreadId = thread.Id();

    // Create a new thread that will be the UI thread
    TName uiThreadName(KSwtUiThreadName);
    RThread uiThread;
    TInt createStatus = uiThread.Create( uiThreadName, SymbianUtils::uiThreadEntryPoint,
            KSwtUiThreadStackSize, NULL, reinterpret_cast<TAny*>(data) );
    data->uiThreadId = uiThread.Id();
    if(createStatus != KErrNone)
        {
        delete data;
        return createStatus;
        }

    // Resume the UI thread and wait until it reports back the initialization
    // status
    uiThread.Resume();
    User::WaitForRequest(data->initStatus);

    // Launch the support thread
    if(data->initStatus == KErrNone)
        {
        startSupportThread(reinterpret_cast<TAny*>(data));
        }

    // Return the thread initialization status
    return data->initStatus.Int();
    }

int SymbianUtils::initUiThread(JNIEnv* aJniEnv, const TInt& aUid)
    {
    // This thread may or may not be a UI thread started by calling startUI.
    // It's concluded that this thread was started by startUI if TLS is set by
    // this DLL. If this is already the UI thread then not much initialization
    // is left to do. If this isn't a UI thread then everything has to be
    // initialized. In the latter case the stack size has already been fixed
    // and that might cause problems.
    TBool isUiThread = ETrue;
    SwtTlsData* data = reinterpret_cast<SwtTlsData*>(Dll::Tls());
    if(!data) isUiThread = EFalse;

    // If this is already initialized as a UI thread by startUI the do nothing
    // more. Otherwise continue with the initialization.
    if(isUiThread)
        {
        return KErrNone;
        }

    // Create and set the TLS data structure.
    data = new (std::nothrow) SwtTlsData();
    if(!data) return KErrNoMemory;
    data->doFreeTLSInCleanupUIThread = ETrue;
    Dll::SetTls(data);

    // This is the UI thread now, store its id
    RThread uiThread;
    data->uiThreadId = uiThread.Id();

    // Get a VM pointer using the current thread context
    JavaVM* javaVM = NULL;
    jint getVMStatus = aJniEnv->GetJavaVM(&javaVM);
    if(getVMStatus < 0) return KErrNoMemory;
    data->vm = javaVM;

    // Store the MIDlet uid we got as a parameter
    data->uid = aUid;

    // Launch the support thread
    startSupportThread(reinterpret_cast<TAny*>(data));

    return KErrNone;
    }

void SymbianUtils::cleanupUiThread()
    {
    SwtTlsData* data = reinterpret_cast<SwtTlsData*>(Dll::Tls());
    if(!data) return;
    if(data->doFreeTLSInCleanupUIThread)
        {
        freeTLSData(data);
        }
    }

void SymbianUtils::setAppName(JNIEnv* aJniEnv, jstring aName)
    {
    // Convert name from jstring to Symbian descriptor
    HBufC16* buffer = NULL;
    if(aName != NULL)
        {
        jboolean isCopy;
        const jchar* javaChars = aJniEnv->GetStringChars(aName, &isCopy);
        if (javaChars)
            {
            AutoReleaseStringChars cleaner(aJniEnv, aName, javaChars);
            jsize length = aJniEnv->GetStringLength(aName);

            TRAPD(err, buffer = HBufC16::NewL(length));
            if (err == KErrNone)
                {
                 TText16* ptr =const_cast<TText16*> (buffer->Des().Ptr());
                 memcpy(ptr, javaChars, length * sizeof(jchar));
                 buffer->Des().SetLength(length);
                }
            else
                {
                throw std::bad_alloc();
                }
            }
        else
            {
            throw std::bad_alloc();
            }
        }

    // Set the name to the window group
    CCoeEnv* coe = CCoeEnv::Static();
    CApaWindowGroupName* wgn = NULL;
    TRAPD( err, wgn = CApaWindowGroupName::NewL(coe->WsSession()));
    if(err == KErrNone)
        {
        SwtTlsData* data = reinterpret_cast<SwtTlsData*>(Dll::Tls());
        wgn->SetAppUid(TUid::Uid(static_cast<TInt>(data->uid)));
        TRAP_IGNORE(wgn->SetCaptionL(*buffer));
        wgn->SetWindowGroupName(coe->RootWin());
        delete wgn;
        }

    delete buffer;
    }

bool SymbianUtils::eventFilter(QObject* object, const TWsEvent* aEvent)
{
    int swtEventType = -1;
    switch (aEvent->Type())
    {
    case KAknShutOrHideApp: //The event is received when exit from task list,
        //which terminates application straight away
        swtEventType = org_eclipse_swt_internal_qt_OS_QSWTEVENT_SYSTEMSHUTDOWN;
        break;
    case EEventUser:
        if ((*reinterpret_cast<TApaSystemEvent*> (aEvent->EventData()))
            == EApaSystemEventShutdown)
        {
            // other system exit (e.g. when out of memory).
            if (!CEikonEnv::Static()->IsSystem())
            {
                swtEventType
                    = org_eclipse_swt_internal_qt_OS_QSWTEVENT_SYSTEMSHUTDOWN;
            }
        }
        break;
    case EEventWindowVisibilityChanged:
    {
        CCoeControl* control =
            reinterpret_cast<CCoeControl*> (aEvent->Handle());
        QWidget* widget = QWidget::find(control);
        if (widget)
        {
            const TWsVisibilityChangedEvent* ev = aEvent->VisibilityChanged();
            if (ev)
            {
                if (ev->iFlags & TWsVisibilityChangedEvent::ENotVisible)
                {
                    swtEventType
                        = org_eclipse_swt_internal_qt_OS_QSWTEVENT_SYMBIAN_WINDOW_NOT_VISIBLE;
                }
                else if (ev->iFlags
                    & TWsVisibilityChangedEvent::EPartiallyVisible)
                {
                    swtEventType
                        = org_eclipse_swt_internal_qt_OS_QSWTEVENT_SYMBIAN_WINDOW_PARTIALLY_VISIBLE;
                }
                else if (ev->iFlags & TWsVisibilityChangedEvent::EFullyVisible)
                {
                    swtEventType
                        = org_eclipse_swt_internal_qt_OS_QSWTEVENT_SYMBIAN_WINDOW_FULLY_VISIBLE;
                }
            }
            if (swtEventType > -1)
            {
                object = widget;
            }
        }

        bool res = eventFilter(object, -1, swtEventType);
        return res;
    }
    default:
        break;
    }
    return eventFilter(object, -1, swtEventType);
}

bool SymbianUtils::eventFilter(QObject* object, const TInt aSymbianType, TInt aSwtType)
    {
    if(aSymbianType > -1)
        {
        switch (aSymbianType)
           {
           case KEikDynamicLayoutVariantSwitch:
               aSwtType = org_eclipse_swt_internal_qt_OS_QSWTEVENT_RESOURCECHANGE;
               break;
           default:
               break;
           }
        }

    if (aSwtType > -1)
        {
        QVariant data = qApp->property(EVENT_FILTER);
        if (data.isValid())
            {
            EventCallback* filter = reinterpret_cast<EventCallback*> (data.toInt());
            if (filter)
                {
                return filter->eventFilter(object, 0, aSwtType);
                }
            }
        }
    return false;
    }

TInt SymbianUtils::GetScreenDeviceNumber()
    {
    return CCoeEnv::Static()->ScreenDevice()->GetScreenNumber();
    }

TInt SymbianUtils::GetColorDepth()
    {
    return TDisplayModeUtils::NumDisplayModeBitsPerPixel(
            CCoeEnv::Static()->ScreenDevice()->DisplayMode());
    }

TInt SymbianUtils::GetHwInputs()
    {
    TInt mask;
    HAL::Get(HALData::EKeyboard, mask);
    return mask;
    }

CApaApplication* SymbianUtils::NewApplication()
    {
    return new SwtQtS60MainApplication;
    }

void SymbianUtils::notifyThreadInitStatus(const TInt& aStatus,
        TThreadId aInitThreadId, TRequestStatus* aStatusPtr)
    {
    RThread initThread;
    TInt openStatus = initThread.Open(aInitThreadId);
    __ASSERT_DEBUG(openStatus == KErrNone, User::Panic(KSwtDllName, 0));
    if(openStatus == KErrNone)
        {
        initThread.RequestComplete(aStatusPtr, aStatus);
        initThread.Close();
        }
    }

TInt SymbianUtils::uiThreadEntryPoint(TAny* aTlsData)
    {
    // Take ownership the the TLS data
    SwtTlsData* data = reinterpret_cast<SwtTlsData*>(aTlsData);
    Dll::SetTls(aTlsData);
    data->doFreeTLSInCleanupUIThread = EFalse;

    // Set the thread as process critical so that the entire process will die
    // if the UI thread panics.
    User::SetCritical(User::EProcessCritical);

    // Create a CleanupStack
    CTrapCleanup* cleanup = CTrapCleanup::New();
    if(!cleanup)
        {
        notifyThreadInitStatus(KErrNoMemory, data->initThreadId, &data->initStatus);
        freeTLSData(data);
        return KErrNoMemory;
        }

    // Attach this thread to the VM to get the JNIEnv pointer.
    JNIEnv* env = NULL;
    void* args = NULL;
    jint attachStatus = data->vm->AttachCurrentThread((void**)&env, args);
    if(attachStatus < 0)
        {
        notifyThreadInitStatus(KErrNoMemory, data->initThreadId, &data->initStatus);
        freeTLSData(data);
        delete cleanup;
        cleanup = NULL;
        return KErrNoMemory;
        }

    // Obtain methodID of run() of the Java callback object
    jclass runnerClass = env->GetObjectClass(data->runner);
    jmethodID mid = NULL;
    if(runnerClass) mid = env->GetMethodID(runnerClass, "run", "()V");

    // Check if something failed
    if(!mid)
        {
        notifyThreadInitStatus(KErrNoMemory, data->initThreadId, &data->initStatus);

        TRAP_IGNORE(data->vm->DetachCurrentThread());
        freeTLSData(data);
        delete cleanup;
        cleanup = NULL;

        return KErrNoMemory;
        }

    // Notify the waiting thread that initialization has completed successfully
    notifyThreadInitStatus(KErrNone, data->initThreadId, &data->initStatus);

    // Call run() of the Java callback object. Inside this call the UI event
    // loop will be executed.
    TRAPD(err, env->CallVoidMethod(data->runner, mid));
    if(err != KErrNone)
        {
        // Something did leave. All Qt APIs are trapped so it might be a Java
        // class library that has failed. This is a fatal error and the process
        // should die.
        User::Panic(KSwtUiThreadName, 0);
        }

    // The application allowed the UI thread to exit. Clean-up and die.

    // Remove the reference to the runner Java object
    TRAP_IGNORE(env->DeleteGlobalRef(data->runner));
    data->runner = NULL;

    // Detach the UI thread from the VM
    TRAP_IGNORE(data->vm->DetachCurrentThread());
    freeTLSData(data);
    delete cleanup;
    cleanup = NULL;

    return KErrNone;
    }

/*
 * Don't trust the JNI implementation to trap everything properly but let's
 * always have a top-level trap also in this thread to avoid panics such as
 * EUSER-CBase 66/69.
 */
TInt SymbianUtils::supportThreadEntryPoint(TAny* aParams)
    {
    CTrapCleanup* cleanup = CTrapCleanup::New();
    int retVal = 0;
    TRAP_IGNORE(retVal = trappedSupportThreadEntryPoint(aParams));
    delete cleanup;
    cleanup = NULL;
    return retVal;
    }

TInt SymbianUtils::trappedSupportThreadEntryPoint(TAny* aParams)
    {
    // Prevent the library from getting detached when the VM closes its handle.
    // That would lead in the destruction of the Qt's global statics in a different
    // thread than they were created in causing problems. Keep a handle to the
    // library in a thread until the process terminates.
    RLibrary libRef;
#ifdef _DEBUG
    TInt addLibRef =
#endif
    libRef.Load(KSwtDllName);
    __ASSERT_DEBUG(addLibRef == KErrNone, User::Panic(KSwtDllName, 0));

    // Store JavaVM pointer and UI thread id from the thread params.
    SwtTlsData* data = reinterpret_cast<SwtTlsData*>(aParams);
    TThreadId uiThreadId = data->uiThreadId;
    JavaVM* vm = data->vm;

    // Try attach using the JavaVM pointer. At this point there's a Java thread
    // waiting ensuring that the VM has not closed down and the JavaVM pointer
    // must be valid.
    JNIEnv* env = NULL;
    void* args = NULL;
    jint attachStatus = vm->AttachCurrentThread((void**)&env, args);
    __ASSERT_DEBUG(attachStatus == 0, User::Panic(KSwtDllName, 0));

    // Notify the waiting Java thread that we have attached and it can continue
    notifyThreadInitStatus(attachStatus, data->initThreadId, &data->initStatus);

    // Create a rendezvous request to detect if the UI thread dies.
    RThread uiThread;
    TInt openStatus = uiThread.Open(uiThreadId);
    __ASSERT_DEBUG(openStatus == KErrNone, User::Panic(KSwtDllName, 0));
    TRequestStatus uiThreadRendezvous;
    if(openStatus == KErrNone)
        {
        uiThread.Rendezvous(uiThreadRendezvous);
        }

    // Wait until the process dies. If the UI thread dies notify MIDP
    // application management software that the MIDlet should die.
    // This is a workaround until QTBUG-5284 is resolved.
    while(ETrue)
        {
        User::WaitForAnyRequest();
        if(vm && openStatus == KErrNone) {
            if(uiThread.ExitType() != EExitPending)
                {
                // Notify once and detach the thread.
                notifyUIThreadExit(env);
                vm->DetachCurrentThread();
                env = NULL;
                vm = NULL;
                }
            }
        }

    // Because the thread is waiting until the process terminates,
    // execution will neve reach here.
    }

void SymbianUtils::startSupportThread(TAny* aParams)
    {
    // Set the thread id that the support thread will notify when it has
    // attached to the VM.
    SwtTlsData* data = reinterpret_cast<SwtTlsData*>(aParams);
    RThread initThread;
    data->initThreadId = initThread.Id();
    data->initStatus = KRequestPending;

    // Launch the support thread
    TName supportThreadName(KSwtSupportThreadName);
    RThread supportThread;
    TInt createStatus = supportThread.Create(supportThreadName, SymbianUtils::supportThreadEntryPoint,
            KSwtSupportThreadStackSize, NULL, aParams);

    // If the application disposed the Display and recreated it in the same
    // thread then support thread already exists.
    if(createStatus == KErrAlreadyExists) return;

    if(createStatus == KErrNone) supportThread.Resume();
    __ASSERT_DEBUG(createStatus == KErrNone, User::Panic(KSwtSupportThreadName, 0));

    // Wait until the support thread is attached to the VM to ensure that
    // the VM doesn't have a chance to unload and invalidate the VM pointer.
    User::WaitForRequest(data->initStatus);
    }

void SymbianUtils::notifyUIThreadExit(JNIEnv* aEnv)
    {
    if(!aEnv) return;
    if(aEnv->ExceptionCheck() == JNI_TRUE) return;

    jclass clazz = aEnv->FindClass("org/eclipse/swt/internal/ExitNotificationWrapper");
    __ASSERT_DEBUG(clazz, User::Panic(KSwtUiThreadName, 0));
    if(clazz)
        {
        jmethodID id = aEnv->GetStaticMethodID(clazz, "uiDisposed", "()V");
        __ASSERT_DEBUG(id, User::Panic(KSwtUiThreadName, 0));
        if(id)
            {
            aEnv->CallStaticVoidMethod(clazz, id);
            }
        aEnv->DeleteLocalRef(clazz);
        }
    }