QAndroidEventDispatcherStopper is stopped when the application is in background and the user uses the task manager to kill the task. If the application has services the task manager doesn't kills it, but instead it tries to gently terminate the activity. The problem is that the activity is still backgrounded (meaning that the Qt event loop is freezed), therefore terminateQt will hang. Task-number: QTBUG-54012 Change-Id: I6e333cbcaf41e9e298eeb8b2b0bc3adcf446783f Reviewed-by: Christian Stromme <christian.stromme@qt.io>
865 lines
31 KiB
C++
865 lines
31 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2014 BogDan Vatra <bogdan@kde.org>
|
|
** Copyright (C) 2015 The Qt Company Ltd.
|
|
** Contact: http://www.qt.io/licensing/
|
|
**
|
|
** This file is part of the plugins of the Qt Toolkit.
|
|
**
|
|
** $QT_BEGIN_LICENSE:LGPL21$
|
|
** Commercial License Usage
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
** accordance with the commercial license agreement provided with the
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
** and conditions see http://www.qt.io/terms-conditions. For further
|
|
** information use the contact form at http://www.qt.io/contact-us.
|
|
**
|
|
** GNU Lesser General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
|
** General Public License version 2.1 or version 3 as published by the Free
|
|
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
|
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
|
** following information to ensure the GNU Lesser General Public License
|
|
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
|
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
|
**
|
|
** As a special exception, The Qt Company gives you certain additional
|
|
** rights. These rights are described in The Qt Company LGPL Exception
|
|
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
|
**
|
|
** $QT_END_LICENSE$
|
|
**
|
|
****************************************************************************/
|
|
|
|
#include <dlfcn.h>
|
|
#include <pthread.h>
|
|
#include <semaphore.h>
|
|
#include <qplugin.h>
|
|
#include <qdebug.h>
|
|
|
|
#include "androidjnimain.h"
|
|
#include "androidjniaccessibility.h"
|
|
#include "androidjniinput.h"
|
|
#include "androidjniclipboard.h"
|
|
#include "androidjnimenu.h"
|
|
#include "androiddeadlockprotector.h"
|
|
#include "qandroidplatformdialoghelpers.h"
|
|
#include "qandroidplatformintegration.h"
|
|
#include "qandroidassetsfileenginehandler.h"
|
|
|
|
#include <android/bitmap.h>
|
|
#include <android/asset_manager_jni.h>
|
|
#include "qandroideventdispatcher.h"
|
|
#include <android/api-level.h>
|
|
|
|
#include <QtCore/private/qjnihelpers_p.h>
|
|
#include <QtCore/private/qjni_p.h>
|
|
#include <QtGui/private/qguiapplication_p.h>
|
|
#include <QtGui/private/qhighdpiscaling_p.h>
|
|
|
|
#include <qpa/qwindowsysteminterface.h>
|
|
|
|
Q_IMPORT_PLUGIN(QAndroidPlatformIntegrationPlugin)
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
static JavaVM *m_javaVM = nullptr;
|
|
static jclass m_applicationClass = nullptr;
|
|
static jobject m_classLoaderObject = nullptr;
|
|
static jmethodID m_loadClassMethodID = nullptr;
|
|
static AAssetManager *m_assetManager = nullptr;
|
|
static jobject m_resourcesObj = nullptr;
|
|
static jobject m_activityObject = nullptr;
|
|
static jmethodID m_createSurfaceMethodID = nullptr;
|
|
static jmethodID m_setSurfaceGeometryMethodID = nullptr;
|
|
static jmethodID m_destroySurfaceMethodID = nullptr;
|
|
|
|
static bool m_activityActive = true; // defaults to true because when the platform plugin is
|
|
// initialized, QtActivity::onResume() has already been called
|
|
|
|
static jclass m_bitmapClass = nullptr;
|
|
static jmethodID m_createBitmapMethodID = nullptr;
|
|
static jobject m_ARGB_8888_BitmapConfigValue = nullptr;
|
|
static jobject m_RGB_565_BitmapConfigValue = nullptr;
|
|
|
|
static bool m_statusBarShowing = true;
|
|
|
|
static jclass m_bitmapDrawableClass = nullptr;
|
|
static jmethodID m_bitmapDrawableConstructorMethodID = nullptr;
|
|
|
|
extern "C" typedef int (*Main)(int, char **); //use the standard main method to start the application
|
|
static Main m_main = nullptr;
|
|
static void *m_mainLibraryHnd = nullptr;
|
|
static QList<QByteArray> m_applicationParams;
|
|
pthread_t m_qtAppThread = 0;
|
|
static sem_t m_exitSemaphore, m_terminateSemaphore;
|
|
|
|
struct SurfaceData
|
|
{
|
|
~SurfaceData() { delete surface; }
|
|
QJNIObjectPrivate *surface = nullptr;
|
|
AndroidSurfaceClient *client = nullptr;
|
|
};
|
|
|
|
QHash<int, AndroidSurfaceClient *> m_surfaces;
|
|
|
|
static QMutex m_surfacesMutex;
|
|
static int m_surfaceId = 1;
|
|
|
|
|
|
static QAndroidPlatformIntegration *m_androidPlatformIntegration = nullptr;
|
|
|
|
static int m_desktopWidthPixels = 0;
|
|
static int m_desktopHeightPixels = 0;
|
|
static double m_scaledDensity = 0;
|
|
static double m_density = 1.0;
|
|
|
|
static volatile bool m_pauseApplication;
|
|
|
|
static AndroidAssetsFileEngineHandler *m_androidAssetsFileEngineHandler = nullptr;
|
|
|
|
|
|
|
|
static const char m_qtTag[] = "Qt";
|
|
static const char m_classErrorMsg[] = "Can't find class \"%s\"";
|
|
static const char m_methodErrorMsg[] = "Can't find method \"%s%s\"";
|
|
|
|
namespace QtAndroid
|
|
{
|
|
void setAndroidPlatformIntegration(QAndroidPlatformIntegration *androidPlatformIntegration)
|
|
{
|
|
m_surfacesMutex.lock();
|
|
m_androidPlatformIntegration = androidPlatformIntegration;
|
|
m_surfacesMutex.unlock();
|
|
}
|
|
|
|
QAndroidPlatformIntegration *androidPlatformIntegration()
|
|
{
|
|
QMutexLocker locker(&m_surfacesMutex);
|
|
return m_androidPlatformIntegration;
|
|
}
|
|
|
|
QWindow *topLevelWindowAt(const QPoint &globalPos)
|
|
{
|
|
return m_androidPlatformIntegration
|
|
? m_androidPlatformIntegration->screen()->topLevelAt(globalPos)
|
|
: 0;
|
|
}
|
|
|
|
int desktopWidthPixels()
|
|
{
|
|
return m_desktopWidthPixels;
|
|
}
|
|
|
|
int desktopHeightPixels()
|
|
{
|
|
return m_desktopHeightPixels;
|
|
}
|
|
|
|
double scaledDensity()
|
|
{
|
|
return m_scaledDensity;
|
|
}
|
|
|
|
double pixelDensity()
|
|
{
|
|
return m_density;
|
|
}
|
|
|
|
JavaVM *javaVM()
|
|
{
|
|
return m_javaVM;
|
|
}
|
|
|
|
AAssetManager *assetManager()
|
|
{
|
|
return m_assetManager;
|
|
}
|
|
|
|
jclass applicationClass()
|
|
{
|
|
return m_applicationClass;
|
|
}
|
|
|
|
jobject activity()
|
|
{
|
|
return m_activityObject;
|
|
}
|
|
|
|
void showStatusBar()
|
|
{
|
|
if (m_statusBarShowing)
|
|
return;
|
|
|
|
QJNIObjectPrivate::callStaticMethod<void>(m_applicationClass, "setFullScreen", "(Z)V", false);
|
|
m_statusBarShowing = true;
|
|
}
|
|
|
|
void hideStatusBar()
|
|
{
|
|
if (!m_statusBarShowing)
|
|
return;
|
|
|
|
QJNIObjectPrivate::callStaticMethod<void>(m_applicationClass, "setFullScreen", "(Z)V", true);
|
|
m_statusBarShowing = false;
|
|
}
|
|
|
|
void setApplicationActive()
|
|
{
|
|
if (m_activityActive)
|
|
QWindowSystemInterface::handleApplicationStateChanged(Qt::ApplicationActive);
|
|
}
|
|
|
|
jobject createBitmap(QImage img, JNIEnv *env)
|
|
{
|
|
if (!m_bitmapClass)
|
|
return 0;
|
|
|
|
if (img.format() != QImage::Format_RGBA8888 && img.format() != QImage::Format_RGB16)
|
|
img = img.convertToFormat(QImage::Format_RGBA8888);
|
|
|
|
jobject bitmap = env->CallStaticObjectMethod(m_bitmapClass,
|
|
m_createBitmapMethodID,
|
|
img.width(),
|
|
img.height(),
|
|
img.format() == QImage::Format_RGBA8888
|
|
? m_ARGB_8888_BitmapConfigValue
|
|
: m_RGB_565_BitmapConfigValue);
|
|
if (!bitmap)
|
|
return 0;
|
|
|
|
AndroidBitmapInfo info;
|
|
if (AndroidBitmap_getInfo(env, bitmap, &info) < 0) {
|
|
env->DeleteLocalRef(bitmap);
|
|
return 0;
|
|
}
|
|
|
|
void *pixels;
|
|
if (AndroidBitmap_lockPixels(env, bitmap, &pixels) < 0) {
|
|
env->DeleteLocalRef(bitmap);
|
|
return 0;
|
|
}
|
|
|
|
if (info.stride == uint(img.bytesPerLine())
|
|
&& info.width == uint(img.width())
|
|
&& info.height == uint(img.height())) {
|
|
memcpy(pixels, img.constBits(), info.stride * info.height);
|
|
} else {
|
|
uchar *bmpPtr = static_cast<uchar *>(pixels);
|
|
const unsigned width = qMin(info.width, (uint)img.width()); //should be the same
|
|
const unsigned height = qMin(info.height, (uint)img.height()); //should be the same
|
|
for (unsigned y = 0; y < height; y++, bmpPtr += info.stride)
|
|
memcpy(bmpPtr, img.constScanLine(y), width);
|
|
}
|
|
AndroidBitmap_unlockPixels(env, bitmap);
|
|
return bitmap;
|
|
}
|
|
|
|
jobject createBitmap(int width, int height, QImage::Format format, JNIEnv *env)
|
|
{
|
|
if (format != QImage::Format_RGBA8888
|
|
&& format != QImage::Format_RGB16)
|
|
return 0;
|
|
|
|
return env->CallStaticObjectMethod(m_bitmapClass,
|
|
m_createBitmapMethodID,
|
|
width,
|
|
height,
|
|
format == QImage::Format_RGB16
|
|
? m_RGB_565_BitmapConfigValue
|
|
: m_ARGB_8888_BitmapConfigValue);
|
|
}
|
|
|
|
jobject createBitmapDrawable(jobject bitmap, JNIEnv *env)
|
|
{
|
|
if (!bitmap || !m_bitmapDrawableClass || !m_resourcesObj)
|
|
return 0;
|
|
|
|
return env->NewObject(m_bitmapDrawableClass,
|
|
m_bitmapDrawableConstructorMethodID,
|
|
m_resourcesObj,
|
|
bitmap);
|
|
}
|
|
|
|
const char *classErrorMsgFmt()
|
|
{
|
|
return m_classErrorMsg;
|
|
}
|
|
|
|
const char *methodErrorMsgFmt()
|
|
{
|
|
return m_methodErrorMsg;
|
|
}
|
|
|
|
const char *qtTagText()
|
|
{
|
|
return m_qtTag;
|
|
}
|
|
|
|
QString deviceName()
|
|
{
|
|
QString manufacturer = QJNIObjectPrivate::getStaticObjectField("android/os/Build", "MANUFACTURER", "Ljava/lang/String;").toString();
|
|
QString model = QJNIObjectPrivate::getStaticObjectField("android/os/Build", "MODEL", "Ljava/lang/String;").toString();
|
|
|
|
return manufacturer + QLatin1Char(' ') + model;
|
|
}
|
|
|
|
int createSurface(AndroidSurfaceClient *client, const QRect &geometry, bool onTop, int imageDepth)
|
|
{
|
|
QJNIEnvironmentPrivate env;
|
|
if (!env)
|
|
return -1;
|
|
|
|
m_surfacesMutex.lock();
|
|
int surfaceId = m_surfaceId++;
|
|
m_surfaces[surfaceId] = client;
|
|
m_surfacesMutex.unlock();
|
|
|
|
jint x = 0, y = 0, w = -1, h = -1;
|
|
if (!geometry.isNull()) {
|
|
x = geometry.x();
|
|
y = geometry.y();
|
|
w = std::max(geometry.width(), 1);
|
|
h = std::max(geometry.height(), 1);
|
|
}
|
|
env->CallStaticVoidMethod(m_applicationClass,
|
|
m_createSurfaceMethodID,
|
|
surfaceId,
|
|
jboolean(onTop),
|
|
x, y, w, h,
|
|
imageDepth);
|
|
return surfaceId;
|
|
}
|
|
|
|
int insertNativeView(jobject view, const QRect &geometry)
|
|
{
|
|
m_surfacesMutex.lock();
|
|
const int surfaceId = m_surfaceId++;
|
|
m_surfaces[surfaceId] = nullptr; // dummy
|
|
m_surfacesMutex.unlock();
|
|
|
|
jint x = 0, y = 0, w = -1, h = -1;
|
|
if (!geometry.isNull())
|
|
geometry.getRect(&x, &y, &w, &h);
|
|
|
|
QJNIObjectPrivate::callStaticMethod<void>(m_applicationClass,
|
|
"insertNativeView",
|
|
"(ILandroid/view/View;IIII)V",
|
|
surfaceId,
|
|
view,
|
|
x,
|
|
y,
|
|
qMax(w, 1),
|
|
qMax(h, 1));
|
|
|
|
return surfaceId;
|
|
}
|
|
|
|
void setViewVisibility(jobject view, bool visible)
|
|
{
|
|
QJNIObjectPrivate::callStaticMethod<void>(m_applicationClass,
|
|
"setViewVisibility",
|
|
"(Landroid/view/View;Z)V",
|
|
view,
|
|
visible);
|
|
}
|
|
|
|
void setSurfaceGeometry(int surfaceId, const QRect &geometry)
|
|
{
|
|
if (surfaceId == -1)
|
|
return;
|
|
|
|
QJNIEnvironmentPrivate env;
|
|
if (!env)
|
|
return;
|
|
jint x = 0, y = 0, w = -1, h = -1;
|
|
if (!geometry.isNull()) {
|
|
x = geometry.x();
|
|
y = geometry.y();
|
|
w = geometry.width();
|
|
h = geometry.height();
|
|
}
|
|
env->CallStaticVoidMethod(m_applicationClass,
|
|
m_setSurfaceGeometryMethodID,
|
|
surfaceId,
|
|
x, y, w, h);
|
|
}
|
|
|
|
|
|
void destroySurface(int surfaceId)
|
|
{
|
|
if (surfaceId == -1)
|
|
return;
|
|
|
|
QMutexLocker lock(&m_surfacesMutex);
|
|
const auto &it = m_surfaces.find(surfaceId);
|
|
if (it != m_surfaces.end())
|
|
m_surfaces.remove(surfaceId);
|
|
|
|
QJNIEnvironmentPrivate env;
|
|
if (!env)
|
|
return;
|
|
|
|
env->CallStaticVoidMethod(m_applicationClass,
|
|
m_destroySurfaceMethodID,
|
|
surfaceId);
|
|
}
|
|
|
|
void bringChildToFront(int surfaceId)
|
|
{
|
|
if (surfaceId == -1)
|
|
return;
|
|
|
|
QJNIObjectPrivate::callStaticMethod<void>(m_applicationClass,
|
|
"bringChildToFront",
|
|
"(I)V",
|
|
surfaceId);
|
|
}
|
|
|
|
void bringChildToBack(int surfaceId)
|
|
{
|
|
if (surfaceId == -1)
|
|
return;
|
|
|
|
QJNIObjectPrivate::callStaticMethod<void>(m_applicationClass,
|
|
"bringChildToBack",
|
|
"(I)V",
|
|
surfaceId);
|
|
}
|
|
|
|
bool blockEventLoopsWhenSuspended()
|
|
{
|
|
static bool block = qEnvironmentVariableIntValue("QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED");
|
|
return block;
|
|
}
|
|
|
|
} // namespace QtAndroid
|
|
|
|
|
|
static jboolean startQtAndroidPlugin(JNIEnv* /*env*/, jobject /*object*//*, jobject applicationAssetManager*/)
|
|
{
|
|
m_androidPlatformIntegration = nullptr;
|
|
m_androidAssetsFileEngineHandler = new AndroidAssetsFileEngineHandler();
|
|
return true;
|
|
}
|
|
|
|
static void *startMainMethod(void */*data*/)
|
|
{
|
|
QVarLengthArray<const char *> params(m_applicationParams.size());
|
|
for (int i = 0; i < m_applicationParams.size(); i++)
|
|
params[i] = static_cast<const char *>(m_applicationParams[i].constData());
|
|
|
|
int ret = m_main(m_applicationParams.length(), const_cast<char **>(params.data()));
|
|
|
|
if (m_mainLibraryHnd) {
|
|
int res = dlclose(m_mainLibraryHnd);
|
|
if (res < 0)
|
|
qWarning() << "dlclose failed:" << dlerror();
|
|
}
|
|
|
|
if (m_applicationClass)
|
|
QJNIObjectPrivate::callStaticMethod<void>(m_applicationClass, "quitApp", "()V");
|
|
|
|
// All attached threads should be detached before returning from this function.
|
|
JavaVM *vm = QtAndroidPrivate::javaVM();
|
|
if (vm != 0)
|
|
vm->DetachCurrentThread();
|
|
|
|
sem_post(&m_terminateSemaphore);
|
|
sem_wait(&m_exitSemaphore);
|
|
sem_destroy(&m_exitSemaphore);
|
|
|
|
// We must call exit() to ensure that all global objects will be destructed
|
|
exit(ret);
|
|
return 0;
|
|
}
|
|
|
|
static jboolean startQtApplication(JNIEnv *env, jobject /*object*/, jstring paramsString, jstring environmentString)
|
|
{
|
|
m_mainLibraryHnd = nullptr;
|
|
{ // Set env. vars
|
|
const char *nativeString = env->GetStringUTFChars(environmentString, 0);
|
|
const QList<QByteArray> envVars = QByteArray(nativeString).split('\t');
|
|
env->ReleaseStringUTFChars(environmentString, nativeString);
|
|
foreach (const QByteArray &envVar, envVars) {
|
|
const QList<QByteArray> envVarPair = envVar.split('=');
|
|
if (envVarPair.size() == 2 && ::setenv(envVarPair[0], envVarPair[1], 1) != 0)
|
|
qWarning() << "Can't set environment" << envVarPair;
|
|
}
|
|
}
|
|
|
|
const char *nativeString = env->GetStringUTFChars(paramsString, 0);
|
|
QByteArray string = nativeString;
|
|
env->ReleaseStringUTFChars(paramsString, nativeString);
|
|
|
|
m_applicationParams=string.split('\t');
|
|
|
|
// Go home
|
|
QDir::setCurrent(QDir::homePath());
|
|
|
|
//look for main()
|
|
if (m_applicationParams.length()) {
|
|
// Obtain a handle to the main library (the library that contains the main() function).
|
|
// This library should already be loaded, and calling dlopen() will just return a reference to it.
|
|
m_mainLibraryHnd = dlopen(m_applicationParams.first().data(), 0);
|
|
if (m_mainLibraryHnd == nullptr) {
|
|
qCritical() << "dlopen failed:" << dlerror();
|
|
return false;
|
|
}
|
|
m_main = (Main)dlsym(m_mainLibraryHnd, "main");
|
|
} else {
|
|
qWarning() << "No main library was specified; searching entire process (this is slow!)";
|
|
m_main = (Main)dlsym(RTLD_DEFAULT, "main");
|
|
}
|
|
|
|
if (!m_main) {
|
|
qCritical() << "dlsym failed:" << dlerror() << endl
|
|
<< "Could not find main method";
|
|
return false;
|
|
}
|
|
|
|
if (sem_init(&m_exitSemaphore, 0, 0) == -1)
|
|
return false;
|
|
|
|
if (sem_init(&m_terminateSemaphore, 0, 0) == -1)
|
|
return false;
|
|
|
|
return pthread_create(&m_qtAppThread, nullptr, startMainMethod, nullptr) == 0;
|
|
}
|
|
|
|
|
|
static void quitQtAndroidPlugin(JNIEnv *env, jclass /*clazz*/)
|
|
{
|
|
Q_UNUSED(env);
|
|
m_androidPlatformIntegration = nullptr;
|
|
delete m_androidAssetsFileEngineHandler;
|
|
m_androidAssetsFileEngineHandler = nullptr;
|
|
}
|
|
|
|
static void terminateQt(JNIEnv *env, jclass /*clazz*/)
|
|
{
|
|
// QAndroidEventDispatcherStopper is stopped when the user uses the task manager to kill the application
|
|
if (!QAndroidEventDispatcherStopper::instance()->stopped()) {
|
|
sem_wait(&m_terminateSemaphore);
|
|
sem_destroy(&m_terminateSemaphore);
|
|
}
|
|
env->DeleteGlobalRef(m_applicationClass);
|
|
env->DeleteGlobalRef(m_classLoaderObject);
|
|
if (m_resourcesObj)
|
|
env->DeleteGlobalRef(m_resourcesObj);
|
|
if (m_activityObject)
|
|
env->DeleteGlobalRef(m_activityObject);
|
|
if (m_bitmapClass)
|
|
env->DeleteGlobalRef(m_bitmapClass);
|
|
if (m_ARGB_8888_BitmapConfigValue)
|
|
env->DeleteGlobalRef(m_ARGB_8888_BitmapConfigValue);
|
|
if (m_RGB_565_BitmapConfigValue)
|
|
env->DeleteGlobalRef(m_RGB_565_BitmapConfigValue);
|
|
if (m_bitmapDrawableClass)
|
|
env->DeleteGlobalRef(m_bitmapDrawableClass);
|
|
m_androidPlatformIntegration = nullptr;
|
|
delete m_androidAssetsFileEngineHandler;
|
|
m_androidAssetsFileEngineHandler = nullptr;
|
|
|
|
if (!QAndroidEventDispatcherStopper::instance()->stopped()) {
|
|
sem_post(&m_exitSemaphore);
|
|
pthread_join(m_qtAppThread, nullptr);
|
|
}
|
|
}
|
|
|
|
static void setSurface(JNIEnv *env, jobject /*thiz*/, jint id, jobject jSurface, jint w, jint h)
|
|
{
|
|
QMutexLocker lock(&m_surfacesMutex);
|
|
const auto &it = m_surfaces.find(id);
|
|
if (it.value() == nullptr) // This should never happen...
|
|
return;
|
|
|
|
if (it == m_surfaces.end()) {
|
|
qWarning()<<"Can't find surface" << id;
|
|
return;
|
|
}
|
|
it.value()->surfaceChanged(env, jSurface, w, h);
|
|
}
|
|
|
|
static void setDisplayMetrics(JNIEnv */*env*/, jclass /*clazz*/,
|
|
jint widthPixels, jint heightPixels,
|
|
jint desktopWidthPixels, jint desktopHeightPixels,
|
|
jdouble xdpi, jdouble ydpi,
|
|
jdouble scaledDensity, jdouble density)
|
|
{
|
|
// Android does not give us the correct screen size for immersive mode, but
|
|
// the surface does have the right size
|
|
|
|
widthPixels = qMax(widthPixels, desktopWidthPixels);
|
|
heightPixels = qMax(heightPixels, desktopHeightPixels);
|
|
|
|
m_desktopWidthPixels = desktopWidthPixels;
|
|
m_desktopHeightPixels = desktopHeightPixels;
|
|
m_scaledDensity = scaledDensity;
|
|
m_density = density;
|
|
|
|
if (!m_androidPlatformIntegration) {
|
|
QAndroidPlatformIntegration::setDefaultDisplayMetrics(desktopWidthPixels,
|
|
desktopHeightPixels,
|
|
qRound(double(widthPixels) / xdpi * 25.4),
|
|
qRound(double(heightPixels) / ydpi * 25.4),
|
|
widthPixels,
|
|
heightPixels);
|
|
} else {
|
|
m_androidPlatformIntegration->setDisplayMetrics(qRound(double(widthPixels) / xdpi * 25.4),
|
|
qRound(double(heightPixels) / ydpi * 25.4));
|
|
m_androidPlatformIntegration->setScreenSize(widthPixels, heightPixels);
|
|
m_androidPlatformIntegration->setDesktopSize(desktopWidthPixels, desktopHeightPixels);
|
|
}
|
|
}
|
|
|
|
static void updateWindow(JNIEnv */*env*/, jobject /*thiz*/)
|
|
{
|
|
if (!m_androidPlatformIntegration)
|
|
return;
|
|
|
|
if (QGuiApplication::instance() != nullptr) {
|
|
foreach (QWindow *w, QGuiApplication::topLevelWindows()) {
|
|
QRect availableGeometry = w->screen()->availableGeometry();
|
|
if (w->geometry().width() > 0 && w->geometry().height() > 0 && availableGeometry.width() > 0 && availableGeometry.height() > 0)
|
|
QWindowSystemInterface::handleExposeEvent(w, QRegion(QRect(QPoint(), w->geometry().size())));
|
|
}
|
|
}
|
|
|
|
QAndroidPlatformScreen *screen = static_cast<QAndroidPlatformScreen *>(m_androidPlatformIntegration->screen());
|
|
if (screen->rasterSurfaces())
|
|
QMetaObject::invokeMethod(screen, "setDirty", Qt::QueuedConnection, Q_ARG(QRect,screen->geometry()));
|
|
}
|
|
|
|
static void updateApplicationState(JNIEnv */*env*/, jobject /*thiz*/, jint state)
|
|
{
|
|
m_activityActive = (state == Qt::ApplicationActive);
|
|
|
|
if (!m_main || !m_androidPlatformIntegration || !QGuiApplicationPrivate::platformIntegration()) {
|
|
QAndroidPlatformIntegration::setDefaultApplicationState(Qt::ApplicationState(state));
|
|
return;
|
|
}
|
|
|
|
if (state == Qt::ApplicationActive)
|
|
QtAndroidPrivate::handleResume();
|
|
else if (state == Qt::ApplicationInactive)
|
|
QtAndroidPrivate::handlePause();
|
|
|
|
if (state <= Qt::ApplicationInactive) {
|
|
// NOTE: sometimes we will receive two consecutive suspended notifications,
|
|
// In the second suspended notification, QWindowSystemInterface::flushWindowSystemEvents()
|
|
// will deadlock since the dispatcher has been stopped in the first suspended notification.
|
|
// To avoid the deadlock we simply return if we found the event dispatcher has been stopped.
|
|
if (QAndroidEventDispatcherStopper::instance()->stopped())
|
|
return;
|
|
|
|
// Don't send timers and sockets events anymore if we are going to hide all windows
|
|
QAndroidEventDispatcherStopper::instance()->goingToStop(true);
|
|
QCoreApplication::processEvents();
|
|
QWindowSystemInterface::handleApplicationStateChanged(Qt::ApplicationState(state));
|
|
{
|
|
AndroidDeadlockProtector protector;
|
|
if (protector.acquire())
|
|
QWindowSystemInterface::flushWindowSystemEvents();
|
|
}
|
|
if (state == Qt::ApplicationSuspended)
|
|
QAndroidEventDispatcherStopper::instance()->stopAll();
|
|
} else {
|
|
QAndroidEventDispatcherStopper::instance()->startAll();
|
|
QWindowSystemInterface::handleApplicationStateChanged(Qt::ApplicationState(state));
|
|
QAndroidEventDispatcherStopper::instance()->goingToStop(false);
|
|
}
|
|
}
|
|
|
|
static void handleOrientationChanged(JNIEnv */*env*/, jobject /*thiz*/, jint newRotation, jint nativeOrientation)
|
|
{
|
|
// Array of orientations rotated in 90 degree increments, counterclockwise
|
|
// (same direction as Android measures angles)
|
|
static const Qt::ScreenOrientation orientations[] = {
|
|
Qt::PortraitOrientation,
|
|
Qt::LandscapeOrientation,
|
|
Qt::InvertedPortraitOrientation,
|
|
Qt::InvertedLandscapeOrientation
|
|
};
|
|
|
|
// The Android API defines the following constants:
|
|
// ROTATION_0 : 0
|
|
// ROTATION_90 : 1
|
|
// ROTATION_180 : 2
|
|
// ROTATION_270 : 3
|
|
// ORIENTATION_PORTRAIT : 1
|
|
// ORIENTATION_LANDSCAPE : 2
|
|
|
|
// and newRotation is how much the current orientation is rotated relative to nativeOrientation
|
|
|
|
// which means that we can be really clever here :)
|
|
Qt::ScreenOrientation screenOrientation = orientations[(nativeOrientation - 1 + newRotation) % 4];
|
|
Qt::ScreenOrientation native = orientations[nativeOrientation - 1];
|
|
|
|
QAndroidPlatformIntegration::setScreenOrientation(screenOrientation, native);
|
|
if (m_androidPlatformIntegration) {
|
|
QPlatformScreen *screen = m_androidPlatformIntegration->screen();
|
|
QWindowSystemInterface::handleScreenOrientationChange(screen->screen(),
|
|
screenOrientation);
|
|
}
|
|
}
|
|
|
|
static void onActivityResult(JNIEnv */*env*/, jclass /*cls*/,
|
|
jint requestCode,
|
|
jint resultCode,
|
|
jobject data)
|
|
{
|
|
QtAndroidPrivate::handleActivityResult(requestCode, resultCode, data);
|
|
}
|
|
|
|
static void onNewIntent(JNIEnv *env, jclass /*cls*/, jobject data)
|
|
{
|
|
QtAndroidPrivate::handleNewIntent(env, data);
|
|
}
|
|
|
|
static JNINativeMethod methods[] = {
|
|
{"startQtAndroidPlugin", "()Z", (void *)startQtAndroidPlugin},
|
|
{"startQtApplication", "(Ljava/lang/String;Ljava/lang/String;)V", (void *)startQtApplication},
|
|
{"quitQtAndroidPlugin", "()V", (void *)quitQtAndroidPlugin},
|
|
{"terminateQt", "()V", (void *)terminateQt},
|
|
{"setDisplayMetrics", "(IIIIDDDD)V", (void *)setDisplayMetrics},
|
|
{"setSurface", "(ILjava/lang/Object;II)V", (void *)setSurface},
|
|
{"updateWindow", "()V", (void *)updateWindow},
|
|
{"updateApplicationState", "(I)V", (void *)updateApplicationState},
|
|
{"handleOrientationChanged", "(II)V", (void *)handleOrientationChanged},
|
|
{"onActivityResult", "(IILandroid/content/Intent;)V", (void *)onActivityResult},
|
|
{"onNewIntent", "(Landroid/content/Intent;)V", (void *)onNewIntent}
|
|
};
|
|
|
|
#define FIND_AND_CHECK_CLASS(CLASS_NAME) \
|
|
clazz = env->FindClass(CLASS_NAME); \
|
|
if (!clazz) { \
|
|
__android_log_print(ANDROID_LOG_FATAL, m_qtTag, m_classErrorMsg, CLASS_NAME); \
|
|
return JNI_FALSE; \
|
|
}
|
|
|
|
#define GET_AND_CHECK_METHOD(VAR, CLASS, METHOD_NAME, METHOD_SIGNATURE) \
|
|
VAR = env->GetMethodID(CLASS, METHOD_NAME, METHOD_SIGNATURE); \
|
|
if (!VAR) { \
|
|
__android_log_print(ANDROID_LOG_FATAL, m_qtTag, m_methodErrorMsg, METHOD_NAME, METHOD_SIGNATURE); \
|
|
return JNI_FALSE; \
|
|
}
|
|
|
|
#define GET_AND_CHECK_STATIC_METHOD(VAR, CLASS, METHOD_NAME, METHOD_SIGNATURE) \
|
|
VAR = env->GetStaticMethodID(CLASS, METHOD_NAME, METHOD_SIGNATURE); \
|
|
if (!VAR) { \
|
|
__android_log_print(ANDROID_LOG_FATAL, m_qtTag, m_methodErrorMsg, METHOD_NAME, METHOD_SIGNATURE); \
|
|
return JNI_FALSE; \
|
|
}
|
|
|
|
#define GET_AND_CHECK_FIELD(VAR, CLASS, FIELD_NAME, FIELD_SIGNATURE) \
|
|
VAR = env->GetFieldID(CLASS, FIELD_NAME, FIELD_SIGNATURE); \
|
|
if (!VAR) { \
|
|
__android_log_print(ANDROID_LOG_FATAL, m_qtTag, m_methodErrorMsg, FIELD_NAME, FIELD_SIGNATURE); \
|
|
return JNI_FALSE; \
|
|
}
|
|
|
|
#define GET_AND_CHECK_STATIC_FIELD(VAR, CLASS, FIELD_NAME, FIELD_SIGNATURE) \
|
|
VAR = env->GetStaticFieldID(CLASS, FIELD_NAME, FIELD_SIGNATURE); \
|
|
if (!VAR) { \
|
|
__android_log_print(ANDROID_LOG_FATAL, m_qtTag, m_methodErrorMsg, FIELD_NAME, FIELD_SIGNATURE); \
|
|
return JNI_FALSE; \
|
|
}
|
|
|
|
static int registerNatives(JNIEnv *env)
|
|
{
|
|
jclass clazz;
|
|
FIND_AND_CHECK_CLASS("org/qtproject/qt5/android/QtNative");
|
|
m_applicationClass = static_cast<jclass>(env->NewGlobalRef(clazz));
|
|
|
|
if (env->RegisterNatives(m_applicationClass, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
|
|
__android_log_print(ANDROID_LOG_FATAL,"Qt", "RegisterNatives failed");
|
|
return JNI_FALSE;
|
|
}
|
|
|
|
GET_AND_CHECK_STATIC_METHOD(m_createSurfaceMethodID, m_applicationClass, "createSurface", "(IZIIIII)V");
|
|
GET_AND_CHECK_STATIC_METHOD(m_setSurfaceGeometryMethodID, m_applicationClass, "setSurfaceGeometry", "(IIIII)V");
|
|
GET_AND_CHECK_STATIC_METHOD(m_destroySurfaceMethodID, m_applicationClass, "destroySurface", "(I)V");
|
|
|
|
jmethodID methodID;
|
|
GET_AND_CHECK_STATIC_METHOD(methodID, m_applicationClass, "activity", "()Landroid/app/Activity;");
|
|
jobject activityObject = env->CallStaticObjectMethod(m_applicationClass, methodID);
|
|
GET_AND_CHECK_STATIC_METHOD(methodID, m_applicationClass, "classLoader", "()Ljava/lang/ClassLoader;");
|
|
m_classLoaderObject = env->NewGlobalRef(env->CallStaticObjectMethod(m_applicationClass, methodID));
|
|
clazz = env->GetObjectClass(m_classLoaderObject);
|
|
GET_AND_CHECK_METHOD(m_loadClassMethodID, clazz, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
|
|
|
|
if (activityObject) {
|
|
m_activityObject = env->NewGlobalRef(activityObject);
|
|
|
|
FIND_AND_CHECK_CLASS("android/content/ContextWrapper");
|
|
GET_AND_CHECK_METHOD(methodID, clazz, "getAssets", "()Landroid/content/res/AssetManager;");
|
|
m_assetManager = AAssetManager_fromJava(env, env->CallObjectMethod(activityObject, methodID));
|
|
|
|
GET_AND_CHECK_METHOD(methodID, clazz, "getResources", "()Landroid/content/res/Resources;");
|
|
m_resourcesObj = env->NewGlobalRef(env->CallObjectMethod(activityObject, methodID));
|
|
|
|
FIND_AND_CHECK_CLASS("android/graphics/Bitmap");
|
|
m_bitmapClass = static_cast<jclass>(env->NewGlobalRef(clazz));
|
|
GET_AND_CHECK_STATIC_METHOD(m_createBitmapMethodID, m_bitmapClass
|
|
, "createBitmap", "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
|
|
FIND_AND_CHECK_CLASS("android/graphics/Bitmap$Config");
|
|
jfieldID fieldId;
|
|
GET_AND_CHECK_STATIC_FIELD(fieldId, clazz, "ARGB_8888", "Landroid/graphics/Bitmap$Config;");
|
|
m_ARGB_8888_BitmapConfigValue = env->NewGlobalRef(env->GetStaticObjectField(clazz, fieldId));
|
|
GET_AND_CHECK_STATIC_FIELD(fieldId, clazz, "RGB_565", "Landroid/graphics/Bitmap$Config;");
|
|
m_RGB_565_BitmapConfigValue = env->NewGlobalRef(env->GetStaticObjectField(clazz, fieldId));
|
|
|
|
FIND_AND_CHECK_CLASS("android/graphics/drawable/BitmapDrawable");
|
|
m_bitmapDrawableClass = static_cast<jclass>(env->NewGlobalRef(clazz));
|
|
GET_AND_CHECK_METHOD(m_bitmapDrawableConstructorMethodID,
|
|
m_bitmapDrawableClass,
|
|
"<init>",
|
|
"(Landroid/content/res/Resources;Landroid/graphics/Bitmap;)V");
|
|
}
|
|
|
|
|
|
|
|
return JNI_TRUE;
|
|
}
|
|
|
|
QT_END_NAMESPACE
|
|
|
|
Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void */*reserved*/)
|
|
{
|
|
static bool initialized = false;
|
|
if (initialized)
|
|
return JNI_VERSION_1_6;
|
|
initialized = true;
|
|
|
|
QT_USE_NAMESPACE
|
|
typedef union {
|
|
JNIEnv *nativeEnvironment;
|
|
void *venv;
|
|
} UnionJNIEnvToVoid;
|
|
|
|
__android_log_print(ANDROID_LOG_INFO, "Qt", "qt start");
|
|
UnionJNIEnvToVoid uenv;
|
|
uenv.venv = nullptr;
|
|
m_javaVM = nullptr;
|
|
|
|
if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
|
|
__android_log_print(ANDROID_LOG_FATAL, "Qt", "GetEnv failed");
|
|
return -1;
|
|
}
|
|
|
|
JNIEnv *env = uenv.nativeEnvironment;
|
|
if (!registerNatives(env)
|
|
|| !QtAndroidInput::registerNatives(env)
|
|
|| !QtAndroidMenu::registerNatives(env)
|
|
|| !QtAndroidAccessibility::registerNatives(env)
|
|
|| !QtAndroidDialogHelpers::registerNatives(env)) {
|
|
__android_log_print(ANDROID_LOG_FATAL, "Qt", "registerNatives failed");
|
|
return -1;
|
|
}
|
|
|
|
m_javaVM = vm;
|
|
return JNI_VERSION_1_4;
|
|
}
|