blob: e0c437be2786204adc9242b71e8b40e9da9c4296 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2012 BogDan Vatra <bogdan@kde.org>
** Contact: https://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** 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 https://www.qt.io/terms-conditions. For further
** information use the contact form at https://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 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qandroidplatformintegration.h"
#include <QtCore/private/qjni_p.h>
#include <QtGui/private/qguiapplication_p.h>
#include <QGuiApplication>
#include <QOpenGLContext>
#include <QThread>
#include <QOffscreenSurface>
#include <QtEglSupport/private/qeglpbuffer_p.h>
#include <qpa/qwindowsysteminterface.h>
#include <qpa/qplatformwindow.h>
#include <qpa/qplatformoffscreensurface.h>
#include "androidjnimain.h"
#include "qabstracteventdispatcher.h"
#include "qandroideventdispatcher.h"
#include "qandroidplatformbackingstore.h"
#include "qandroidplatformaccessibility.h"
#include "qandroidplatformclipboard.h"
#include "qandroidplatformforeignwindow.h"
#include "qandroidplatformfontdatabase.h"
#include "qandroidplatformopenglcontext.h"
#include "qandroidplatformopenglwindow.h"
#include "qandroidplatformscreen.h"
#include "qandroidplatformservices.h"
#include "qandroidplatformtheme.h"
#include "qandroidsystemlocale.h"
#include "qandroidplatformoffscreensurface.h"
#include <QtPlatformHeaders/QEGLNativeContext>
#if QT_CONFIG(vulkan)
#include "qandroidplatformvulkanwindow.h"
#include "qandroidplatformvulkaninstance.h"
#endif
QT_BEGIN_NAMESPACE
int QAndroidPlatformIntegration::m_defaultGeometryWidth = 320;
int QAndroidPlatformIntegration::m_defaultGeometryHeight = 455;
int QAndroidPlatformIntegration::m_defaultScreenWidth = 320;
int QAndroidPlatformIntegration::m_defaultScreenHeight = 455;
int QAndroidPlatformIntegration::m_defaultPhysicalSizeWidth = 50;
int QAndroidPlatformIntegration::m_defaultPhysicalSizeHeight = 71;
Qt::ScreenOrientation QAndroidPlatformIntegration::m_orientation = Qt::PrimaryOrientation;
Qt::ScreenOrientation QAndroidPlatformIntegration::m_nativeOrientation = Qt::PrimaryOrientation;
bool QAndroidPlatformIntegration::m_showPasswordEnabled = false;
void *QAndroidPlatformNativeInterface::nativeResourceForIntegration(const QByteArray &resource)
{
if (resource=="JavaVM")
return QtAndroid::javaVM();
if (resource == "QtActivity")
return QtAndroid::activity();
if (resource == "QtService")
return QtAndroid::service();
if (resource == "AndroidStyleData") {
if (m_androidStyle) {
if (m_androidStyle->m_styleData.isEmpty())
m_androidStyle->m_styleData = AndroidStyle::loadStyleData();
return &m_androidStyle->m_styleData;
}
else
return nullptr;
}
if (resource == "AndroidStandardPalette") {
if (m_androidStyle)
return &m_androidStyle->m_standardPalette;
else
return nullptr;
}
if (resource == "AndroidQWidgetFonts") {
if (m_androidStyle)
return &m_androidStyle->m_QWidgetsFonts;
else
return nullptr;
}
if (resource == "AndroidDeviceName") {
static QString deviceName = QtAndroid::deviceName();
return &deviceName;
}
return 0;
}
void *QAndroidPlatformNativeInterface::nativeResourceForWindow(const QByteArray &resource, QWindow *window)
{
#if QT_CONFIG(vulkan)
if (resource == "vkSurface") {
if (window->surfaceType() == QSurface::VulkanSurface) {
QAndroidPlatformVulkanWindow *w = static_cast<QAndroidPlatformVulkanWindow *>(window->handle());
// return a pointer to the VkSurfaceKHR, not the value
return w ? w->vkSurface() : nullptr;
}
}
#else
Q_UNUSED(resource);
Q_UNUSED(window);
#endif
return nullptr;
}
void QAndroidPlatformNativeInterface::customEvent(QEvent *event)
{
if (event->type() != QEvent::User)
return;
QMutexLocker lock(QtAndroid::platformInterfaceMutex());
QAndroidPlatformIntegration *api = static_cast<QAndroidPlatformIntegration *>(QGuiApplicationPrivate::platformIntegration());
QtAndroid::setAndroidPlatformIntegration(api);
api->flushPendingUpdates();
}
QAndroidPlatformIntegration::QAndroidPlatformIntegration(const QStringList &paramList)
: m_touchDevice(nullptr)
#ifndef QT_NO_ACCESSIBILITY
, m_accessibility(nullptr)
#endif
{
Q_UNUSED(paramList);
m_androidPlatformNativeInterface = new QAndroidPlatformNativeInterface();
m_eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (Q_UNLIKELY(m_eglDisplay == EGL_NO_DISPLAY))
qFatal("Could not open egl display");
EGLint major, minor;
if (Q_UNLIKELY(!eglInitialize(m_eglDisplay, &major, &minor)))
qFatal("Could not initialize egl display");
if (Q_UNLIKELY(!eglBindAPI(EGL_OPENGL_ES_API)))
qFatal("Could not bind GL_ES API");
m_primaryScreen = new QAndroidPlatformScreen();
QWindowSystemInterface::handleScreenAdded(m_primaryScreen);
m_primaryScreen->setPhysicalSize(QSize(m_defaultPhysicalSizeWidth, m_defaultPhysicalSizeHeight));
m_primaryScreen->setSize(QSize(m_defaultScreenWidth, m_defaultScreenHeight));
m_primaryScreen->setAvailableGeometry(QRect(0, 0, m_defaultGeometryWidth, m_defaultGeometryHeight));
m_mainThread = QThread::currentThread();
m_androidFDB = new QAndroidPlatformFontDatabase();
m_androidPlatformServices = new QAndroidPlatformServices();
#ifndef QT_NO_CLIPBOARD
m_androidPlatformClipboard = new QAndroidPlatformClipboard();
#endif
m_androidSystemLocale = new QAndroidSystemLocale;
#ifndef QT_NO_ACCESSIBILITY
m_accessibility = new QAndroidPlatformAccessibility();
#endif // QT_NO_ACCESSIBILITY
QJNIObjectPrivate javaActivity(QtAndroid::activity());
if (!javaActivity.isValid())
javaActivity = QtAndroid::service();
if (javaActivity.isValid()) {
QJNIObjectPrivate resources = javaActivity.callObjectMethod("getResources", "()Landroid/content/res/Resources;");
QJNIObjectPrivate configuration = resources.callObjectMethod("getConfiguration", "()Landroid/content/res/Configuration;");
int touchScreen = configuration.getField<jint>("touchscreen");
if (touchScreen == QJNIObjectPrivate::getStaticField<jint>("android/content/res/Configuration", "TOUCHSCREEN_FINGER")
|| touchScreen == QJNIObjectPrivate::getStaticField<jint>("android/content/res/Configuration", "TOUCHSCREEN_STYLUS"))
{
m_touchDevice = new QTouchDevice;
m_touchDevice->setType(QTouchDevice::TouchScreen);
m_touchDevice->setCapabilities(QTouchDevice::Position
| QTouchDevice::Area
| QTouchDevice::Pressure
| QTouchDevice::NormalizedPosition);
QJNIObjectPrivate pm = javaActivity.callObjectMethod("getPackageManager", "()Landroid/content/pm/PackageManager;");
Q_ASSERT(pm.isValid());
if (pm.callMethod<jboolean>("hasSystemFeature","(Ljava/lang/String;)Z",
QJNIObjectPrivate::getStaticObjectField("android/content/pm/PackageManager", "FEATURE_TOUCHSCREEN_MULTITOUCH_JAZZHAND", "Ljava/lang/String;").object())) {
m_touchDevice->setMaximumTouchPoints(10);
} else if (pm.callMethod<jboolean>("hasSystemFeature","(Ljava/lang/String;)Z",
QJNIObjectPrivate::getStaticObjectField("android/content/pm/PackageManager", "FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT", "Ljava/lang/String;").object())) {
m_touchDevice->setMaximumTouchPoints(4);
} else if (pm.callMethod<jboolean>("hasSystemFeature","(Ljava/lang/String;)Z",
QJNIObjectPrivate::getStaticObjectField("android/content/pm/PackageManager", "FEATURE_TOUCHSCREEN_MULTITOUCH", "Ljava/lang/String;").object())) {
m_touchDevice->setMaximumTouchPoints(2);
}
QWindowSystemInterface::registerTouchDevice(m_touchDevice);
}
auto contentResolver = javaActivity.callObjectMethod("getContentResolver", "()Landroid/content/ContentResolver;");
Q_ASSERT(contentResolver.isValid());
QJNIObjectPrivate txtShowPassValue = QJNIObjectPrivate::callStaticObjectMethod("android/provider/Settings$System",
"getString",
"(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;",
contentResolver.object(),
QJNIObjectPrivate::getStaticObjectField("android/provider/Settings$System", "TEXT_SHOW_PASSWORD", "Ljava/lang/String;").object());
if (txtShowPassValue.isValid()) {
bool ok = false;
const int txtShowPass = txtShowPassValue.toString().toInt(&ok);
m_showPasswordEnabled = ok ? (txtShowPass == 1) : false;
}
}
// We can't safely notify the jni bridge that we're up and running just yet, so let's postpone
// it for now.
QCoreApplication::postEvent(m_androidPlatformNativeInterface, new QEvent(QEvent::User));
}
static bool needsBasicRenderloopWorkaround()
{
static bool needsWorkaround =
QtAndroid::deviceName().compare(QLatin1String("samsung SM-T211"), Qt::CaseInsensitive) == 0
|| QtAndroid::deviceName().compare(QLatin1String("samsung SM-T210"), Qt::CaseInsensitive) == 0
|| QtAndroid::deviceName().compare(QLatin1String("samsung SM-T215"), Qt::CaseInsensitive) == 0;
return needsWorkaround;
}
bool QAndroidPlatformIntegration::hasCapability(Capability cap) const
{
switch (cap) {
case ApplicationState: return true;
case ThreadedPixmaps: return true;
case NativeWidgets: return QtAndroid::activity();
case OpenGL: return QtAndroid::activity();
case ForeignWindows: return QtAndroid::activity();
case ThreadedOpenGL: return !needsBasicRenderloopWorkaround() && QtAndroid::activity();
case RasterGLSurface: return QtAndroid::activity();
case TopStackedNativeChildWindows: return false;
default:
return QPlatformIntegration::hasCapability(cap);
}
}
QPlatformBackingStore *QAndroidPlatformIntegration::createPlatformBackingStore(QWindow *window) const
{
if (!QtAndroid::activity())
return nullptr;
return new QAndroidPlatformBackingStore(window);
}
QPlatformOpenGLContext *QAndroidPlatformIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const
{
if (!QtAndroid::activity())
return nullptr;
QSurfaceFormat format(context->format());
format.setAlphaBufferSize(8);
format.setRedBufferSize(8);
format.setGreenBufferSize(8);
format.setBlueBufferSize(8);
auto ctx = new QAndroidPlatformOpenGLContext(format, context->shareHandle(), m_eglDisplay, context->nativeHandle());
context->setNativeHandle(QVariant::fromValue<QEGLNativeContext>(QEGLNativeContext(ctx->eglContext(), m_eglDisplay)));
return ctx;
}
QPlatformOffscreenSurface *QAndroidPlatformIntegration::createPlatformOffscreenSurface(QOffscreenSurface *surface) const
{
if (!QtAndroid::activity())
return nullptr;
QSurfaceFormat format(surface->requestedFormat());
format.setAlphaBufferSize(8);
format.setRedBufferSize(8);
format.setGreenBufferSize(8);
format.setBlueBufferSize(8);
if (surface->nativeHandle()) {
// Adopt existing offscreen Surface
// The expectation is that nativeHandle is an ANativeWindow* representing
// an android.view.Surface
return new QAndroidPlatformOffscreenSurface(m_eglDisplay, format, surface);
}
return new QEGLPbuffer(m_eglDisplay, format, surface);
}
QPlatformWindow *QAndroidPlatformIntegration::createPlatformWindow(QWindow *window) const
{
if (!QtAndroid::activity())
return nullptr;
#if QT_CONFIG(vulkan)
if (window->surfaceType() == QSurface::VulkanSurface)
return new QAndroidPlatformVulkanWindow(window);
#endif
return new QAndroidPlatformOpenGLWindow(window, m_eglDisplay);
}
QPlatformWindow *QAndroidPlatformIntegration::createForeignWindow(QWindow *window, WId nativeHandle) const
{
return new QAndroidPlatformForeignWindow(window, nativeHandle);
}
QAbstractEventDispatcher *QAndroidPlatformIntegration::createEventDispatcher() const
{
return new QAndroidEventDispatcher;
}
QAndroidPlatformIntegration::~QAndroidPlatformIntegration()
{
if (m_eglDisplay != EGL_NO_DISPLAY)
eglTerminate(m_eglDisplay);
delete m_androidPlatformNativeInterface;
delete m_androidFDB;
delete m_androidSystemLocale;
#ifndef QT_NO_CLIPBOARD
delete m_androidPlatformClipboard;
#endif
QtAndroid::setAndroidPlatformIntegration(NULL);
}
QPlatformFontDatabase *QAndroidPlatformIntegration::fontDatabase() const
{
return m_androidFDB;
}
#ifndef QT_NO_CLIPBOARD
QPlatformClipboard *QAndroidPlatformIntegration::clipboard() const
{
return m_androidPlatformClipboard;
}
#endif
QPlatformInputContext *QAndroidPlatformIntegration::inputContext() const
{
return &m_platformInputContext;
}
QPlatformNativeInterface *QAndroidPlatformIntegration::nativeInterface() const
{
return m_androidPlatformNativeInterface;
}
QPlatformServices *QAndroidPlatformIntegration::services() const
{
return m_androidPlatformServices;
}
QVariant QAndroidPlatformIntegration::styleHint(StyleHint hint) const
{
switch (hint) {
case PasswordMaskDelay:
// this number is from a hard-coded value in Android code (cf. PasswordTransformationMethod)
return m_showPasswordEnabled ? 1500 : 0;
case ShowIsMaximized:
return true;
default:
return QPlatformIntegration::styleHint(hint);
}
}
Qt::WindowState QAndroidPlatformIntegration::defaultWindowState(Qt::WindowFlags flags) const
{
// Don't maximize dialogs on Android
if (flags & Qt::Dialog & ~Qt::Window)
return Qt::WindowNoState;
return QPlatformIntegration::defaultWindowState(flags);
}
static const QLatin1String androidThemeName("android");
QStringList QAndroidPlatformIntegration::themeNames() const
{
return QStringList(QString(androidThemeName));
}
QPlatformTheme *QAndroidPlatformIntegration::createPlatformTheme(const QString &name) const
{
if (androidThemeName == name)
return new QAndroidPlatformTheme(m_androidPlatformNativeInterface);
return 0;
}
void QAndroidPlatformIntegration::setDefaultDisplayMetrics(int gw, int gh, int sw, int sh, int screenWidth, int screenHeight)
{
m_defaultGeometryWidth = gw;
m_defaultGeometryHeight = gh;
m_defaultPhysicalSizeWidth = sw;
m_defaultPhysicalSizeHeight = sh;
m_defaultScreenWidth = screenWidth;
m_defaultScreenHeight = screenHeight;
}
void QAndroidPlatformIntegration::setDefaultDesktopSize(int gw, int gh)
{
m_defaultGeometryWidth = gw;
m_defaultGeometryHeight = gh;
}
void QAndroidPlatformIntegration::setScreenOrientation(Qt::ScreenOrientation currentOrientation,
Qt::ScreenOrientation nativeOrientation)
{
m_orientation = currentOrientation;
m_nativeOrientation = nativeOrientation;
}
void QAndroidPlatformIntegration::flushPendingUpdates()
{
m_primaryScreen->setPhysicalSize(QSize(m_defaultPhysicalSizeWidth,
m_defaultPhysicalSizeHeight));
m_primaryScreen->setSize(QSize(m_defaultScreenWidth, m_defaultScreenHeight));
m_primaryScreen->setAvailableGeometry(QRect(0, 0, m_defaultGeometryWidth, m_defaultGeometryHeight));
}
#ifndef QT_NO_ACCESSIBILITY
QPlatformAccessibility *QAndroidPlatformIntegration::accessibility() const
{
return m_accessibility;
}
#endif
void QAndroidPlatformIntegration::setDesktopSize(int width, int height)
{
if (m_primaryScreen)
QMetaObject::invokeMethod(m_primaryScreen, "setAvailableGeometry", Qt::AutoConnection, Q_ARG(QRect, QRect(0,0,width, height)));
}
void QAndroidPlatformIntegration::setDisplayMetrics(int width, int height)
{
if (m_primaryScreen)
QMetaObject::invokeMethod(m_primaryScreen, "setPhysicalSize", Qt::AutoConnection, Q_ARG(QSize, QSize(width, height)));
}
void QAndroidPlatformIntegration::setScreenSize(int width, int height)
{
if (m_primaryScreen)
QMetaObject::invokeMethod(m_primaryScreen, "setSize", Qt::AutoConnection, Q_ARG(QSize, QSize(width, height)));
}
#if QT_CONFIG(vulkan)
QPlatformVulkanInstance *QAndroidPlatformIntegration::createPlatformVulkanInstance(QVulkanInstance *instance) const
{
return new QAndroidPlatformVulkanInstance(instance);
}
#endif // QT_CONFIG(vulkan)
QT_END_NAMESPACE